diff --git a/conf/sample.config.toml b/conf/sample.config.toml index 7fde3d1..7b4fa38 100644 --- a/conf/sample.config.toml +++ b/conf/sample.config.toml @@ -8,6 +8,8 @@ # more than one running on the same server, this ID will be shown in the WebUI. # could be set also with SNMPCOL_GENERAL_INSTANCE_ID env var instanceID = "WAN Communicactions" + location = "my_location" + description = "SNMP Instance running at localhost" # datadir set the directory where the data will be placed , also sqlite db if set as db engine # if not set the default datadir will be placed in the configuration directory diff --git a/conf/snmpcollector_schema.sql b/conf/snmpcollector_schema.sql index abf820c..45abd29 100644 --- a/conf/snmpcollector_schema.sql +++ b/conf/snmpcollector_schema.sql @@ -1,6 +1,6 @@ CREATE TABLE `influx_cfg` (`id` TEXT NULL, `host` TEXT NULL, `port` INTEGER NULL, `db` TEXT NULL, `user` TEXT NULL, `password` TEXT NULL, `retention` TEXT NULL); CREATE UNIQUE INDEX `UQE_influx_cfg_id` ON `influx_cfg` (`id`); -CREATE TABLE `snmp_device_cfg` (`id` TEXT NULL, `host` TEXT NULL, `port` INTEGER NULL, `retries` INTEGER NULL, `timeout` INTEGER NULL, `repeat` INTEGER NULL, `snmpversion` TEXT NULL, `community` TEXT NULL, `v3seclevel` TEXT NULL, `v3authuser` TEXT NULL, `v3authpass` TEXT NULL, `v3authprot` TEXT NULL, `v3privpass` TEXT NULL, `v3privprot` TEXT NULL, `freq` INTEGER NULL, `outdb` TEXT NULL, `loglevel` TEXT NULL, `logfile` TEXT NULL, `snmpdebug` INTEGER NULL, `devicetagname` TEXT NULL, `devicetagvalue` TEXT NULL, `extra-tags` TEXT NULL); +CREATE TABLE `snmp_device_cfg` (`id` TEXT NULL, `host` TEXT NULL, `port` INTEGER NULL, `retries` INTEGER NULL, `timeout` INTEGER NULL, `repeat` INTEGER NULL, `snmpversion` TEXT NULL, `community` TEXT NULL, `v3seclevel` TEXT NULL, `v3authuser` TEXT NULL, `v3authpass` TEXT NULL, `v3authprot` TEXT NULL, `v3privpass` TEXT NULL, `v3privprot` TEXT NULL, `freq` INTEGER NULL, `outdb` TEXT NULL, `loglevel` TEXT NULL, `logfile` TEXT NULL, `snmpdebug` INTEGER NULL, `devicetagname` TEXT NULL, `devicetagvalue` TEXT NULL, `extra-tags` TEXT NULL, `location` TEXT NULL); CREATE UNIQUE INDEX `UQE_snmp_device_cfg_id` ON `snmp_device_cfg` (`id`); CREATE TABLE `snmp_metric_cfg` (`id` TEXT NULL, `field_name` TEXT NULL, `description` TEXT NULL, `baseoid` TEXT NULL, `datasrctype` TEXT NULL, `getrate` INTEGER NULL, `scale` REAL NULL, `shift` REAL NULL, `istag` INTEGER NULL DEFAULT 0); CREATE UNIQUE INDEX `UQE_snmp_metric_cfg_id` ON `snmp_metric_cfg` (`id`); diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index ce95152..ca1eb98 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -289,7 +289,7 @@ func AddDeviceInRuntime(k string, cfg *config.SnmpDeviceCfg) { // LoadConf loads the DB conf and initializes the device metric config. func LoadConf() { - MainConfig.Database.LoadDbConfig(&DBConfig) + MainConfig.Database.LoadDbConfig(&DBConfig, MainConfig.General.Location) influxdb = PrepareInfluxDBs() // begin self monitoring process if needed, before all goroutines diff --git a/pkg/config/database.go b/pkg/config/database.go index ee59dad..c63eead 100644 --- a/pkg/config/database.go +++ b/pkg/config/database.go @@ -2,6 +2,7 @@ package config import ( "fmt" + "net" "os" "strconv" "strings" @@ -43,7 +44,7 @@ type DbObjAction struct { } //InitDB initialize de BD configuration -func (dbc *DatabaseCfg) InitDB() error { +func (dbc *DatabaseCfg) InitDB(cfg *GeneralConfig) error { // Create ORM engine and database var err error var dbtype string @@ -159,6 +160,103 @@ func (dbc *DatabaseCfg) InitDB() error { if err = dbc.x.Sync(new(OidConditionCfg)); err != nil { log.Fatalf("Fail to sync database OidConditionCfg: %v\n", err) } + if err = dbc.x.Sync(new(PollerLocationCfg)); err != nil { + log.Fatalf("Fail to sync database PollerLocationCfg: %v\n", err) + } + //Lookup if PollerLocation if got Instance_ID else create one + var locationsFound []*PollerLocationCfg + if err = dbc.x.Where("Instance_ID = '" + cfg.InstanceID + "'").Find(&locationsFound); err != nil { + log.Fatalf("There were an error when looking for %s in PollerLocationCfg. Error: %+v", cfg.InstanceID, err) + } else { + if len(locationsFound) == 0 { + //Create Location + + //Get Hostname + hostname, err := os.Hostname() + if err != nil { + hostname = cfg.InstanceID + } + //Get external IP + ip := getExternalIp() + //Get Location or assign InstanceID + loc := cfg.Location + if len(loc) == 0 { + loc = cfg.InstanceID + } + + var location = PollerLocationCfg{ + ID: cfg.InstanceID, + Location: loc, + Instance_ID: cfg.InstanceID, + Active: true, + Hostname: hostname, + IP: ip.String(), + Description: cfg.Description, + } + + var affected int64 + session := dbc.x.NewSession() + defer session.Close() + affected, err = session.Insert(&location) + if err != nil { + log.Fatalf("There were an error when creating default PollerLocationCfg Error: %+v", err) + session.Rollback() + } else { + log.Infof("PollerLocationCfg created succefully. %+v", affected) + } + } else { + log.Debug("Instance_ID founded on PollerLocationCfg. Checking if any param was changed") + for _, instance := range locationsFound { + log.Debugf("--> %+v\n", instance) + } + //Check if instance params was changed + instance := locationsFound[0] + hasChanges := false + //IP + if instance.IP != getExternalIp().String() { + instance.IP = getExternalIp().String() + hasChanges = true + } + //Description + if instance.Description != cfg.Description { + instance.Description = cfg.Description + hasChanges = true + } + //Hostname + hostname, err := os.Hostname() + if err != nil { + hostname = cfg.InstanceID + } + if instance.Hostname != hostname { + instance.Description = hostname + hasChanges = true + } + //Location + loc := cfg.Location + if len(loc) == 0 { + loc = cfg.InstanceID + } + if instance.Location != loc { + instance.Location = loc + hasChanges = true + } + + if hasChanges { + log.Debug("Updating with newer values...") + var affected int64 + session := dbc.x.NewSession() + defer session.Close() + //affected, err := session.ID(instance.ID).AllCols().Update() + affected, err = session.Where("Instance_ID ='" + instance.Instance_ID + "'").AllCols().Update(instance) + if err != nil { + log.Fatalf("There were an error when updating PollerLocationCfg Error: %+v", err) + session.Rollback() + } else { + log.Infof("PollerLocationCfg updated succefully. %+v", affected) + } + } + } + } return nil } @@ -189,7 +287,7 @@ func CatalogVar2Map(cv map[string]*VarCatalogCfg) map[string]interface{} { } //LoadDbConfig get data from database -func (dbc *DatabaseCfg) LoadDbConfig(cfg *DBConfig) { +func (dbc *DatabaseCfg) LoadDbConfig(cfg *DBConfig, Location string) { var err error //Load Global Variables VarCatalog := make(map[string]*VarCatalogCfg) @@ -232,10 +330,23 @@ func (dbc *DatabaseCfg) LoadDbConfig(cfg *DBConfig) { } //Device - - cfg.SnmpDevice, err = dbc.GetSnmpDeviceCfgMap("") + //Parameters inside GetSnmpDeviceCfgMap are filter to get devices. All devices who match with location will be returned + cfg.SnmpDevice, err = dbc.GetSnmpDeviceCfgMap("location = '" + Location + "'") if err != nil { log.Warningf("Some errors on get SnmpDeviceConf :%v", err) } dbc.resetChanges() } + +//Utils +func getExternalIp() net.IP { + conn, err := net.Dial("udp", "8.8.8.8:80") + if err != nil { + log.Fatal(err) + } + defer conn.Close() + + localAddr := conn.LocalAddr().(*net.UDPAddr) + + return localAddr.IP +} diff --git a/pkg/config/dbconfig.go b/pkg/config/dbconfig.go index b3bc327..7f0188f 100644 --- a/pkg/config/dbconfig.go +++ b/pkg/config/dbconfig.go @@ -45,6 +45,8 @@ type SnmpDeviceCfg struct { //Filters for measurements MeasurementGroups []string `xorm:"-"` MeasFilters []string `xorm:"-"` + //Federated SNMPC + Location string `xorm:"'location' default 'Collector1'"` } // InfluxCfg is the main configuration for any InfluxDB TSDB @@ -131,6 +133,17 @@ type SnmpDevMGroups struct { IDMGroupCfg string `xorm:"id_mgroup_cfg"` } +// SnmpDevMGroups Mgroups defined on each SnmpDevice +type PollerLocationCfg struct { + ID string `xorm:"'id' unique" binding:"Required"` + Location string `xorm:"location"` + Instance_ID string `xorm:"instance_id"` + Active bool `xorm:"active"` + Hostname string `xorm:"hostname"` + IP string `xorm:"ip"` + Description string `xorm:"description"` +} + // DBConfig read from DB type DBConfig struct { Metrics map[string]*SnmpMetricCfg @@ -138,6 +151,7 @@ type DBConfig struct { MFilters map[string]*MeasFilterCfg GetGroups map[string]*MGroupsCfg SnmpDevice map[string]*SnmpDeviceCfg + Location map[string]*PollerLocationCfg Influxdb map[string]*InfluxCfg VarCatalog map[string]interface{} } diff --git a/pkg/config/mainconfig.go b/pkg/config/mainconfig.go index b4a728d..7ac70de 100644 --- a/pkg/config/mainconfig.go +++ b/pkg/config/mainconfig.go @@ -6,12 +6,14 @@ import ( // GeneralConfig has miscellaneous configuration options type GeneralConfig struct { - InstanceID string `mapstructure:"instanceID" envconfig:"SNMPCOL_GENERAL_INSTANCE_ID"` - LogDir string `mapstructure:"logdir" envconfig:"SNMPCOL_GENERAL_LOG_DIR"` - HomeDir string `mapstructure:"homedir" envconfig:"SNMPCOL_GENERAL_HOME_DIR"` - DataDir string `mapstructure:"datadir" envconfig:"SNMPCOL_GENERAL_DATA_DIR" ` - LogLevel string `mapstructure:"loglevel" envconfig:"SNMPCOL_GENERAL_LOG_LEVEL"` - LogMode string `mapstructure:"log_mode" envconfig:"SNMPCOL_GENERAL_LOG_MODE"` + InstanceID string `mapstructure:"instanceID" envconfig:"SNMPCOL_GENERAL_INSTANCE_ID"` + LogDir string `mapstructure:"logdir" envconfig:"SNMPCOL_GENERAL_LOG_DIR"` + HomeDir string `mapstructure:"homedir" envconfig:"SNMPCOL_GENERAL_HOME_DIR"` + DataDir string `mapstructure:"datadir" envconfig:"SNMPCOL_GENERAL_DATA_DIR" ` + LogLevel string `mapstructure:"loglevel" envconfig:"SNMPCOL_GENERAL_LOG_LEVEL"` + LogMode string `mapstructure:"log_mode" envconfig:"SNMPCOL_GENERAL_LOG_MODE"` + Location string `mapstructure:"location" envconfig:"SNMPCOL_GENERAL_LOCATION"` + Description string `mapstructure:"description" envconfig:"SNMPCOL_GENERAL_DESCRIPTION"` } //DatabaseCfg de configuration for the database diff --git a/pkg/config/pollerlocationcfg.go b/pkg/config/pollerlocationcfg.go new file mode 100644 index 0000000..44efff3 --- /dev/null +++ b/pkg/config/pollerlocationcfg.go @@ -0,0 +1,171 @@ +package config + +import ( + "fmt" + "strings" +) + +/*************************** +Global Var + -GetPollerLocationCfgCfgByLocation(struct) + -GetPollerLocationCfgMap (map - for interna config use + -GetPollerLocationCfgArray(Array - for web ui use ) + -AddPollerLocationCfg + -DelPollerLocationCfg + -UpdatePollerLocationCfg + -GetPollerLocationCfgAffectOnDel +***********************************/ + +/*GetPollerLocationCfgByID get metric data by id*/ +func (dbc *DatabaseCfg) GetPollerLocationCfgByID(id string) (PollerLocationCfg, error) { + cfgarray, err := dbc.GetPollerLocationCfgArray("id='" + id + "'") + if err != nil { + return PollerLocationCfg{}, err + } + if len(cfgarray) > 1 { + return PollerLocationCfg{}, fmt.Errorf("Error %d results on get PollerLocationCfg by id %s", len(cfgarray), id) + } + if len(cfgarray) == 0 { + return PollerLocationCfg{}, fmt.Errorf("Error no values have been returned with this id %s in the Global Var config table", id) + } + return *cfgarray[0], nil +} + +/*GetPollerLocationCfgMap return data in map format*/ +func (dbc *DatabaseCfg) GetPollerLocationCfgMap(filter string) (map[string]*PollerLocationCfg, error) { + cfgarray, err := dbc.GetPollerLocationCfgArray(filter) + cfgmap := make(map[string]*PollerLocationCfg) + for _, val := range cfgarray { + cfgmap[val.ID] = val + //log.Debugf("%+v", *val) + } + return cfgmap, err +} + +/*GetPollerLocationCfgArray generate an array of metrics with all its information */ +func (dbc *DatabaseCfg) GetPollerLocationCfgArray(filter string) ([]*PollerLocationCfg, error) { + var err error + var devices []*PollerLocationCfg + //Get Only data for selected metrics + if len(filter) > 0 { + if err = dbc.x.Where(filter).Find(&devices); err != nil { + log.Warnf("Fail to get PollerLocationCfg data filteter with %s : %v\n", filter, err) + return nil, err + } + } else { + if err = dbc.x.Find(&devices); err != nil { + log.Warnf("Fail to get PollerLocationCfg data: %v\n", err) + return nil, err + } + } + return devices, nil +} + +/*AddPollerLocationCfg for adding new Global Variable*/ +func (dbc *DatabaseCfg) AddPollerLocationCfg(dev PollerLocationCfg) (int64, error) { + var err error + var affected int64 + + // initialize data persistence + session := dbc.x.NewSession() + defer session.Close() + + affected, err = session.Insert(dev) + if err != nil { + session.Rollback() + return 0, err + } + //no other relation + err = session.Commit() + if err != nil { + return 0, err + } + log.Infof("Added new Global Variable Successfully with id %s ", dev.ID) + dbc.addChanges(affected) + return affected, nil +} + +/*DelPollerLocationCfg for deleting influx databases from ID*/ +func (dbc *DatabaseCfg) DelPollerLocationCfg(id string) (int64, error) { + var affecteddev, affected int64 + var err error + + session := dbc.x.NewSession() + defer session.Close() + // deleting references in Measurements + + affected, err = session.Where("id='" + id + "'").Delete(&PollerLocationCfg{}) + if err != nil { + session.Rollback() + return 0, err + } + + err = session.Commit() + if err != nil { + return 0, err + } + log.Infof("Deleted Successfully Global Var with ID %s [ %d Measurements Affected ]", id, affecteddev) + dbc.addChanges(affecteddev) + return affected, nil +} + +/*UpdatePollerLocationCfg for adding new influxdb*/ +func (dbc *DatabaseCfg) UpdatePollerLocationCfg(id string, dev PollerLocationCfg) (int64, error) { + var affecteddev, affected int64 + var err error + // create PollerLocationCfg to check if any configuration issue found before persist to database. + + session := dbc.x.NewSession() + defer session.Close() + + if id != dev.ID { //ID has been changed + var metrics []*SnmpMetricCfg + session.Where("datasrctype = 'STRINGEVAL' and extradata like '%" + id + "%'").Find(&metrics) + for _, v := range metrics { + v.ExtraData = strings.Replace(v.ExtraData, id, dev.ID, -1) + _, err = session.Where("id='" + v.ID + "'").UseBool().AllCols().Update(v) + if err != nil { + session.Rollback() + return 0, err + } + log.Infof("Updated STRING EVAL Metric %s devices old variable name %s new %s", v.ID, dev.ID, id) + } + log.Infof("Updated PollerLocationiableConfig to %d devices ", affecteddev) + } + + affected, err = session.Where("id='" + id + "'").UseBool().AllCols().Update(dev) + if err != nil { + session.Rollback() + return 0, err + } + err = session.Commit() + if err != nil { + return 0, err + } + + log.Infof("Updated PollerLocationiableConfig Successfully with id %s and data:%+v, affected", id, dev) + dbc.addChanges(affected + affecteddev) + return affected, nil +} + +/*GetPollerLocationCfgAffectOnDel for deleting devices from ID*/ +func (dbc *DatabaseCfg) GetPollerLocationCfgAffectOnDel(id string) ([]*DbObjAction, error) { + //var devices []*MeasurementFieldCfg + var obj []*DbObjAction + /* + if err := dbc.x.Where("id_metric_cfg='" + id + "'").Find(&devices); err != nil { + log.Warnf("Error on Get Snmp Metric Cfg id %d for devices , error: %s", id, err) + return nil, err + } + + for _, val := range devices { + obj = append(obj, &DbObjAction{ + Type: "measurementcfg", + TypeDesc: "Measurements", + ObID: val.IDMeasurementCfg, + Action: "Delete PollerLocationiablefield from Measurement relation", + }) + + }*/ + return obj, nil +} diff --git a/pkg/main.go b/pkg/main.go index 55a0c63..b23a115 100644 --- a/pkg/main.go +++ b/pkg/main.go @@ -224,7 +224,7 @@ func main() { }() - agent.MainConfig.Database.InitDB() + agent.MainConfig.Database.InitDB(&agent.MainConfig.General) measurement.SetDB(&agent.MainConfig.Database) impexp.SetDB(&agent.MainConfig.Database) diff --git a/pkg/webui/apicfg-pollerlocations.go b/pkg/webui/apicfg-pollerlocations.go new file mode 100644 index 0000000..737b46a --- /dev/null +++ b/pkg/webui/apicfg-pollerlocations.go @@ -0,0 +1,100 @@ +package webui + +import ( + "github.com/go-macaron/binding" + "github.com/toni-moreno/snmpcollector/pkg/agent" + "github.com/toni-moreno/snmpcollector/pkg/config" + "gopkg.in/macaron.v1" +) + +// NewAPICfgPollerLocation PollerLocation API REST creator +func NewAPICfgPollerLocation(m *macaron.Macaron) error { + + bind := binding.Bind + + m.Group("/api/cfg/pollerlocations", func() { + m.Get("/", reqSignedIn, GetPollerLocation) + m.Post("/", reqSignedIn, bind(config.PollerLocationCfg{}), AddPollerLocation) + m.Put("/:id", reqSignedIn, bind(config.PollerLocationCfg{}), UpdatePollerLocation) + m.Delete("/:id", reqSignedIn, DeletePollerLocation) + m.Get("/:id", reqSignedIn, GetPollerLocationByID) + m.Get("/checkondel/:id", reqSignedIn, GetInfluxAffectOnDel) + }) + + return nil +} + +// GetPollerLocation Return Server Array +func GetPollerLocation(ctx *Context) { + cfgarray, err := agent.MainConfig.Database.GetPollerLocationCfgArray("") + if err != nil { + ctx.JSON(404, err.Error()) + log.Errorf("Error on get PollerLocationiable :%+s", err) + return + } + ctx.JSON(200, &cfgarray) + log.Debugf("Getting Gloval Variable s %+v", &cfgarray) +} + +// AddPollerLocation Insert new global var into the database +func AddPollerLocation(ctx *Context, dev config.PollerLocationCfg) { + log.Printf("ADDING Global Variable %+v", dev) + affected, err := agent.MainConfig.Database.AddPollerLocationCfg(dev) + if err != nil { + log.Warningf("Error on insert new Global Variable %s , affected : %+v , error: %s", dev.ID, affected, err) + ctx.JSON(404, err.Error()) + } else { + //TODO: review if needed return data or affected + ctx.JSON(200, &dev) + } +} + +// UpdatePollerLocation --pending-- +func UpdatePollerLocation(ctx *Context, dev config.PollerLocationCfg) { + id := ctx.Params(":id") + log.Debugf("Tying to update: %+v", dev) + affected, err := agent.MainConfig.Database.UpdatePollerLocationCfg(id, dev) + if err != nil { + log.Warningf("Error on update Global Variable %s , affected : %+v , error: %s", dev.ID, affected, err) + } else { + //TODO: review if needed return device data + ctx.JSON(200, &dev) + } +} + +//DeletePollerLocation --pending-- +func DeletePollerLocation(ctx *Context) { + id := ctx.Params(":id") + log.Debugf("Trying to delete: %+v", id) + affected, err := agent.MainConfig.Database.DelPollerLocationCfg(id) + if err != nil { + log.Warningf("Error on delete Global Variable %s , affected : %+v , error: %s", id, affected, err) + ctx.JSON(404, err.Error()) + } else { + ctx.JSON(200, "deleted") + } +} + +//GetPollerLocationByID --pending-- +func GetPollerLocationByID(ctx *Context) { + id := ctx.Params(":id") + dev, err := agent.MainConfig.Database.GetPollerLocationCfgByID(id) + if err != nil { + log.Warningf("Error on get gloval variable %s , error: %s", id, err) + ctx.JSON(404, err.Error()) + } else { + ctx.JSON(200, &dev) + } +} + +//GetPollerLocationAffectOnDel --pending-- +func GetPollerLocationAffectOnDel(ctx *Context) { + id := ctx.Params(":id") + obarray, err := agent.MainConfig.Database.GetPollerLocationCfgAffectOnDel(id) + if err != nil { + log.Warningf("Error on get object array for influx device %s , error: %s", id, err) + ctx.JSON(404, err.Error()) + } else { + ctx.JSON(200, &obarray) + } +} diff --git a/pkg/webui/webserver.go b/pkg/webui/webserver.go index 9ef395f..6cf5541 100644 --- a/pkg/webui/webserver.go +++ b/pkg/webui/webserver.go @@ -180,6 +180,8 @@ func WebServer(publicPath string, httpListen string, cfg *config.HTTPConfig, id NewAPICfgVarCatalog(m) + NewAPICfgPollerLocation(m) + NewAPICfgOidCondition(m) NewAPICfgSnmpMetric(m) diff --git a/src/main.module.ts b/src/main.module.ts index 654e61c..76934e5 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -22,6 +22,7 @@ import { PasswordToggleDirective } from './common/custom-directives' import { TableActions } from './common/table-actions'; //snmpcollector components +import { PollerLocationCfgComponent } from './pollerlocations/pollerlocations.component'; import { VarCatalogCfgComponent } from './varcatalog/varcatalogcfg.component'; import { SnmpDeviceCfgComponent } from './snmpdevice/snmpdevicecfg.component'; import { OidConditionCfgComponent } from './oidcondition/oidconditioncfg.component'; @@ -82,6 +83,7 @@ import { SpinnerComponent } from './common/spinner'; InfluxServerCfgComponent, CustomFilterCfgComponent, VarCatalogCfgComponent, + PollerLocationCfgComponent, TableListComponent, RuntimeComponent, GenericModal, diff --git a/src/pollerlocations/pollerlocationeditor.html b/src/pollerlocations/pollerlocationeditor.html new file mode 100644 index 0000000..3a6776d --- /dev/null +++ b/src/pollerlocations/pollerlocationeditor.html @@ -0,0 +1,2 @@ +