From dc53b3aaf8c8f9745f88f897fedbabe6b8d40292 Mon Sep 17 00:00:00 2001 From: imbytecat Date: Thu, 22 Aug 2024 16:48:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E6=AD=A5=E9=9B=86=E6=88=90=20?= =?UTF-8?q?zabbix=20agent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 15 +++ config.yaml | 5 + config/config.go | 9 +- config/integration.go | 9 ++ go.mod | 5 + go.sum | 14 ++- integration/zabbixagent/main.go | 63 +++++++++++ .../zabbixagent/plugin/handler/handler.go | 43 ++++++++ integration/zabbixagent/plugin/plguin.go | 102 ++++++++++++++++++ main.go | 26 +++-- router/handler/onvif/connection.go | 9 +- service/onvif/connection.go | 8 +- 12 files changed, 289 insertions(+), 19 deletions(-) create mode 100644 README.md create mode 100644 config/integration.go create mode 100644 integration/zabbixagent/main.go create mode 100644 integration/zabbixagent/plugin/handler/handler.go create mode 100644 integration/zabbixagent/plugin/plguin.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..9ca3fd4 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Wukong: Simplified Management of ONVIF IP Devices + +Wukong is a robust implementation of the ONVIF protocol designed for the efficient management of ONVIF-compliant IP devices, including cameras. This project aims to provide a user-friendly and streamlined solution for the convenient management of IP cameras and other devices that adhere to the ONVIF standard. + +With Wukong, users can easily configure, monitor, and control their ONVIF devices, enhancing the overall experience of managing surveillance systems and ensuring seamless integration within various environments. + +## Requirements + +- Zabbix agent 2 version 6.0.0 or newer +- Go programming language version 1.20 or newer (required only for building the plugin from the source) + +## Installation + +The plugin can be compiled using `go build`. + diff --git a/config.yaml b/config.yaml index 7c1779a..9fed5b5 100644 --- a/config.yaml +++ b/config.yaml @@ -4,3 +4,8 @@ server: app: url: "http://localhost:8080" + +integrations: + zabbix_agent: + plugin: + name: "Onvif" diff --git a/config/config.go b/config/config.go index 6d835d9..5d3b3e9 100644 --- a/config/config.go +++ b/config/config.go @@ -8,8 +8,9 @@ import ( ) type config struct { - Server serverConfig `mapstructure:"server"` - App appConfig `mapstructure:"app"` + Server serverConfig `mapstructure:"server"` + App appConfig `mapstructure:"app"` + Integrations integrationConfig `mapstructure:"integrations"` } type serverConfig struct { @@ -21,6 +22,10 @@ type appConfig struct { URL string `mapstructure:"url"` } +type integrationConfig struct { + ZabbixAgent IntegrationConfig `mapstructure:"zabbix_agent"` +} + var Conf config func LoadConfig() error { diff --git a/config/integration.go b/config/integration.go new file mode 100644 index 0000000..6983a63 --- /dev/null +++ b/config/integration.go @@ -0,0 +1,9 @@ +package config + +type IntegrationConfig struct { + Plugin PluginConfig `mapstructure:"plugin"` +} + +type PluginConfig struct { + Name string `mapstructure:"name"` +} diff --git a/go.mod b/go.mod index ca3a3e8..2b7b14b 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,11 @@ require ( github.com/IOTechSystems/onvif v1.1.2 github.com/gin-gonic/gin v1.10.0 github.com/spf13/viper v1.19.0 + golang.zabbix.com/sdk v1.2.2-0.20240801124644-68a74164363d ) require ( + github.com/Microsoft/go-winio v0.6.0 // indirect github.com/beevik/etree v1.4.0 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect @@ -47,9 +49,12 @@ require ( golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.24.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.26.0 // indirect + golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/protobuf v1.34.1 // 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 7ba0517..f5e6134 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/IOTechSystems/onvif v1.1.2 h1:czmvGY+0oGHwsczDqh5aRatp/PIil8vYHJckljLp4iI= github.com/IOTechSystems/onvif v1.1.2/go.mod h1:4pizduM+kbnuiJpd9xBgVX5/hsZPcH9bj/iZUeCvYy8= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/beevik/etree v1.4.0 h1:oz1UedHRepuY3p4N5OjE0nK1WLCqtzHf25bxplKOHLs= github.com/beevik/etree v1.4.0/go.mod h1:cyWiXwGoasx60gHvtnEh5x8+uIjUVnjWqBvEnhnqKDA= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= @@ -38,8 +40,8 @@ github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBEx github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= @@ -116,14 +118,22 @@ golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.zabbix.com/sdk v1.2.2-0.20240801124644-68a74164363d h1:ApDsjHJ3/hQ8U/QBww/9bYoeAAm3fx5XFc/YBJz/a1c= +golang.zabbix.com/sdk v1.2.2-0.20240801124644-68a74164363d/go.mod h1:KFfJAnUsBIfLemmPVz7TJPliJAwYUSuj4v3n5W1qrCQ= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/integration/zabbixagent/main.go b/integration/zabbixagent/main.go new file mode 100644 index 0000000..b197e9f --- /dev/null +++ b/integration/zabbixagent/main.go @@ -0,0 +1,63 @@ +package zabbixagent + +import ( + "errors" + "fmt" + "onvif-agent/config" + "onvif-agent/integration/zabbixagent/plugin" + "os" + + "golang.zabbix.com/sdk/plugin/flag" + "golang.zabbix.com/sdk/zbxerr" +) + +const copyrightMessage = // +`Copyright 2001-%d imbytecat + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +` +const ( + pluginVersionMajor = 1 + pluginVersionMinor = 0 + pluginVersionPatch = 0 + pluginVersionRC = "alpha1" + pluginLicenseYear = 2024 +) + +func Run() error { + err := flag.HandleFlags( + config.Conf.Integrations.ZabbixAgent.Plugin.Name, + os.Args[0], + fmt.Sprintf(copyrightMessage, pluginLicenseYear), + pluginVersionRC, + pluginVersionMajor, + pluginVersionMinor, + pluginVersionPatch, + ) + if err != nil { + if errors.Is(err, zbxerr.ErrorOSExitZero) { + return nil + } + + return err + } + + err = plugin.Launch() + if err != nil { + return err + } + + return nil +} diff --git a/integration/zabbixagent/plugin/handler/handler.go b/integration/zabbixagent/plugin/handler/handler.go new file mode 100644 index 0000000..8bdd124 --- /dev/null +++ b/integration/zabbixagent/plugin/handler/handler.go @@ -0,0 +1,43 @@ +package handler + +import ( + "context" + "net/http" + "os" +) + +// HandlerFunc describes the signature all metric handler functions must have. +type HandlerFunc func( + ctx context.Context, + metricParams map[string]string, + extraParams ...string, +) (any, error) + +// Handler hold client and syscall implementation for request functions. +type Handler struct { + client *http.Client + sysCalls systemCalls +} + +type systemCalls interface { + environ() []string + lookupEnv(key string) (string, bool) +} + +type osWrapper struct{} + +// New creates a new handler with initialized clients for system and tcp calls. +func New() *Handler { + return &Handler{ + client: http.DefaultClient, + sysCalls: osWrapper{}, + } +} + +func (osWrapper) environ() []string { + return os.Environ() +} + +func (osWrapper) lookupEnv(key string) (string, bool) { + return os.LookupEnv(key) +} diff --git a/integration/zabbixagent/plugin/plguin.go b/integration/zabbixagent/plugin/plguin.go new file mode 100644 index 0000000..119a8fd --- /dev/null +++ b/integration/zabbixagent/plugin/plguin.go @@ -0,0 +1,102 @@ +package plugin + +import ( + "golang.zabbix.com/sdk/errs" + "golang.zabbix.com/sdk/metric" + "golang.zabbix.com/sdk/plugin" + "golang.zabbix.com/sdk/plugin/container" + "onvif-agent/config" + "onvif-agent/integration/zabbixagent/plugin/handler" +) + +var ( + Name = config.Conf.Integrations.ZabbixAgent.Plugin.Name +) + +type onvifMetricKey string + +type onvifMetric struct { + metric *metric.Metric + handler handler.HandlerFunc +} + +type onvifPlugin struct { + plugin.Base + metrics map[onvifMetricKey]*onvifMetric +} + +// Launch launches the plugin. Blocks until plugin execution has finished. +func Launch() error { + p := &onvifPlugin{} + + err := p.registerMetrics() + if err != nil { + return err + } + + h, err := container.NewHandler(Name) + if err != nil { + return errs.Wrap(err, "failed to create new handler") + } + + p.Logger = &h + + err = h.Execute() + if err != nil { + return errs.Wrap(err, "failed to execute plugin handler") + } + + return nil +} + +// Start starts the example plugin. Is required for plugin to match runner interface. +func (p *onvifPlugin) Start() { + p.Logger.Infof("Start called") +} + +// Stop stops the example plugin. Is required for plugin to match runner interface. +func (p *onvifPlugin) Stop() { + p.Logger.Infof("Stop called") +} + +func (p *onvifPlugin) registerMetrics() error { + //h := handler.New() + + p.metrics = map[onvifMetricKey]*onvifMetric{ + //myIPMetric: { + // metric: metric.New( + // "Returns the availability groups.", + // params.Params, + // false, + // ), + // handler: handler.WithJSONResponse( + // handler.WithCredentialValidation( + // h.MyIP, + // ), + // ), + //}, + //goEnvMetric: { + // metric: metric.New( + // "Returns the result rows of a custom query.", + // params.Params, + // true, + // ), + // handler: handler.WithJSONResponse( + // handler.WithCredentialValidation(handler.GoEnvironment), + // ), + //}, + } + + metricSet := metric.MetricSet{} + + for k, m := range p.metrics { + metricSet[string(k)] = m.metric + } + + err := plugin.RegisterMetrics(p, Name, metricSet.List()...) + if err != nil { + return errs.Wrap(err, "failed to register metrics") + } + + return nil +} diff --git a/main.go b/main.go index 9fc4380..7121d4e 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "github.com/gin-gonic/gin" "log" "onvif-agent/config" + "onvif-agent/integration/zabbixagent" "onvif-agent/router" ) @@ -12,17 +13,30 @@ func main() { if err := config.LoadConfig(); err != nil { log.Fatalf("Error loading config: %v", err) } - addr := fmt.Sprintf("%s:%d", config.Conf.Server.Host, config.Conf.Server.Port) - - r := gin.Default() - router.SetupRoutes(r) + /** + * Web server + */ go func() { - err := r.Run(addr) - if err != nil { + r := gin.Default() + + router.SetupRoutes(r) + + addr := fmt.Sprintf("%s:%d", config.Conf.Server.Host, config.Conf.Server.Port) + if err := r.Run(addr); err != nil { fmt.Println("Failed to start server:", err) } }() + /** + * Zabbix agent + */ + go func() { + err := zabbixagent.Run() + if err != nil { + fmt.Println("Failed to start Zabbix agent integration:", err) + } + }() + select {} } diff --git a/router/handler/onvif/connection.go b/router/handler/onvif/connection.go index ba00740..4ed6f86 100644 --- a/router/handler/onvif/connection.go +++ b/router/handler/onvif/connection.go @@ -4,7 +4,6 @@ import ( "github.com/gin-gonic/gin" "net/http" "onvif-agent/service/onvif" - "strings" ) var conns = make(map[string]*onvif.Connection) @@ -22,13 +21,7 @@ func CreateConnection(c *gin.Context) { return } - // 规范化连接地址 - xaddr := req.Xaddr - if !strings.Contains(xaddr, ":") { - xaddr += ":80" - } - - conn, err := onvif.NewConnection(xaddr, req.Username, req.Password) + conn, err := onvif.New(req.Xaddr, req.Username, req.Password) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "message": err.Error(), diff --git a/service/onvif/connection.go b/service/onvif/connection.go index 2f66db3..0932624 100644 --- a/service/onvif/connection.go +++ b/service/onvif/connection.go @@ -2,13 +2,19 @@ package onvif import ( "github.com/IOTechSystems/onvif" + "strings" ) type Connection struct { Device *onvif.Device `json:"device"` } -func NewConnection(xaddr string, username string, password string) (*Connection, error) { +func New(xaddr string, username string, password string) (*Connection, error) { + // 规范化连接地址 + if !strings.Contains(xaddr, ":") { + xaddr += ":80" + } + dev, err := onvif.NewDevice(onvif.DeviceParams{ Xaddr: xaddr, Username: username,