/* ** Zabbix ** Copyright (C) 2001-2022 Zabbix SIA ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. **/ package mongodb import ( "encoding/json" "errors" "strings" "git.zabbix.com/ap/plugin-support/zbxerr" "gopkg.in/mgo.v2/bson" ) const ( statePrimary = 1 stateSecondary = 2 ) const nodeHealthy = 1 type Member struct { health int name string optime int ptr interface{} state int } type rawMember = map[string]interface{} var errUnknownStructure = errors.New("failed to parse the members structure") func parseMembers(raw []interface{}) (result []Member, err error) { var ( members []Member primaryNode Member ) for _, m := range raw { member := Member{} ok := true if v, ok := m.(rawMember)["name"].(string); ok { member.name = v } if v, ok := m.(rawMember)["health"].(float64); ok { member.health = int(v) } if v, ok := m.(rawMember)["optime"].(map[string]interface{}); ok { if ts, ok := v["ts"].(bson.MongoTimestamp); ok { member.optime = int(ts >> 32) } else { member.optime = int(int64(v["ts"].(float64)) >> 32) } } if v, ok := m.(rawMember)["state"].(int); ok { member.state = v } if !ok { return nil, errUnknownStructure } member.ptr = m if member.state == statePrimary { primaryNode = member } else { members = append(members, member) } } result = append([]Member{primaryNode}, members...) if len(result) == 0 { return nil, errUnknownStructure } return result, nil } func injectExtendedMembersStats(raw []interface{}) error { members, err := parseMembers(raw) if err != nil { return err } unhealthyNodes := []string{} unhealthyCount := 0 primary := members[0] for _, node := range members { node.ptr.(rawMember)["lag"] = primary.optime - node.optime if node.state == stateSecondary && node.health != nodeHealthy { unhealthyNodes = append(unhealthyNodes, node.name) unhealthyCount++ } } primary.ptr.(rawMember)["unhealthyNodes"] = unhealthyNodes primary.ptr.(rawMember)["unhealthyCount"] = unhealthyCount primary.ptr.(rawMember)["totalNodes"] = len(members) - 1 return nil } // replSetStatusHandler // https://docs.mongodb.com/manual/reference/command/replSetGetStatus/index.html func replSetStatusHandler(s Session, _ map[string]string) (interface{}, error) { var replSetGetStatus map[string]interface{} err := s.DB("admin").Run(&bson.D{ bson.DocElem{ Name: "replSetGetStatus", Value: 1, }, bson.DocElem{ Name: "maxTimeMS", Value: s.GetMaxTimeMS(), }, }, &replSetGetStatus) if err != nil { if strings.Contains(err.Error(), "not running with --replSet") { return "{}", nil } return nil, zbxerr.ErrorCannotFetchData.Wrap(err) } err = injectExtendedMembersStats(replSetGetStatus["members"].([]interface{})) if err != nil { return nil, zbxerr.ErrorCannotParseResult.Wrap(err) } jsonRes, err := json.Marshal(replSetGetStatus) if err != nil { return nil, zbxerr.ErrorCannotMarshalJSON.Wrap(err) } return string(jsonRes), nil }