diff --git a/models/yang/annotations/sonic-alarm-annot.yang b/models/yang/annotations/sonic-alarm-annot.yang new file mode 100644 index 000000000..62c812080 --- /dev/null +++ b/models/yang/annotations/sonic-alarm-annot.yang @@ -0,0 +1,39 @@ +module sonic-alarm-annot { + + yang-version "1"; + + namespace "http://openconfig.net/yang/annotation/sonic-alarm-annot"; + prefix "salarm-annot"; + + import sonic-alarm { prefix salarm; } + import sonic-extensions { prefix sonic-ext; } + + deviation /salarm:show-alarms { + deviate add { + sonic-ext:rpc-callback "rpc_get_alarms"; + } + } + deviation /salarm:acknowledge-alarms { + deviate add { + sonic-ext:rpc-callback "rpc_acknowledge_alarms"; + } + } + + deviation /salarm:unacknowledge-alarms { + deviate add { + sonic-ext:rpc-callback "rpc_unacknowledge_alarms"; + } + } + + deviation /salarm:sonic-alarm/salarm:ALARM/salarm:ALARM_LIST { + deviate add { + sonic-ext:db-name "EVENT_DB"; + } + } + + deviation /salarm:sonic-alarm/salarm:ALARM_STATS/salarm:ALARM_STATS_LIST { + deviate add { + sonic-ext:db-name "EVENT_DB"; + } + } +} diff --git a/models/yang/annotations/sonic-event-annot.yang b/models/yang/annotations/sonic-event-annot.yang new file mode 100644 index 000000000..7b8d4c38b --- /dev/null +++ b/models/yang/annotations/sonic-event-annot.yang @@ -0,0 +1,29 @@ +module sonic-event-annot { + + yang-version "1"; + + namespace "http://openconfig.net/yang/annotation/sonic-event-annot"; + prefix "sevent-annot"; + + import sonic-event { prefix sevent; } + import sonic-extensions { prefix sonic-ext; } + + deviation /sevent:show-events { + deviate add { + sonic-ext:rpc-callback "rpc_get_events"; + } + } + + deviation /sevent:sonic-event/sevent:EVENT/sevent:EVENT_LIST { + deviate add { + sonic-ext:db-name "EVENT_DB"; + } + } + + deviation /sevent:sonic-event/sevent:EVENT_STATS/sevent:EVENT_STATS_LIST { + deviate add { + sonic-ext:db-name "EVENT_DB"; + } + } + +} diff --git a/models/yang/annotations/sonic-evprofile-annot.yang b/models/yang/annotations/sonic-evprofile-annot.yang new file mode 100644 index 000000000..4059ed477 --- /dev/null +++ b/models/yang/annotations/sonic-evprofile-annot.yang @@ -0,0 +1,23 @@ +module sonic-evprofile-annot { + + yang-version "1"; + + namespace "http://openconfig.net/Azure/sonic-evprofile-annot"; + prefix "evprofile-annot"; + + import sonic-extensions { prefix sonic-ext; } + import sonic-evprofile { prefix sep; } + + deviation /sep:get-evprofile { + deviate add { + sonic-ext:rpc-callback "rpc_getevprofile_cb"; + } + } + + deviation /sep:set-evprofile { + deviate add { + sonic-ext:rpc-callback "rpc_setevprofile_cb"; + } + } + +} diff --git a/models/yang/sonic/sonic-alarm.yang b/models/yang/sonic/sonic-alarm.yang new file mode 100644 index 000000000..f0ce8798b --- /dev/null +++ b/models/yang/sonic/sonic-alarm.yang @@ -0,0 +1,283 @@ + module sonic-alarm { + namespace "http://github.com/Azure/sonic-alarm"; + prefix salarm; + yang-version 1.1; + + + import ietf-yang-types { + prefix yang-types; + } + + import sonic-event { prefix event;} + + // meta + organization + "SONiC"; + + contact + "SONiC"; + + description + "This module defines operational state data for SONiC alarms."; + + revision "2021-01-15" { + description + "Initial revision."; + } + + + grouping alarm-state { + + leaf id { + type string; + description "Sequence identifier for an alarm."; + } + + leaf resource { + type string; + description "The item that is under alarm within the device."; + } + + leaf text { + type string; + description "Dynamic message raised with the alarm."; + } + + leaf time-created { + type event:timeticks64; + description + "The time at which the alarm was raised by the system. + This value is expressed date-and-time format."; + } + + leaf type-id { + type string; + description "Type of the alarm raised"; + } + + leaf severity { + type event:severity-type; + description + "Severity of a raised condition."; + } + + leaf acknowledged { + type boolean; + description + "This denotes whether an alarm is acknowledged by the operator. + An acknowledged alarm is not considered in determining the + health of the system."; + } + + leaf acknowledge-time { + type event:timeticks64; + description + "The time at which alarm is acknowledged by the system. + This value is expressed as nanoseconds since the Unix Epoch."; + } + + } + + container sonic-alarm { + + container ALARM { + + list ALARM_LIST { + key "id"; + uses alarm-state; + } + } + + container ALARM_STATS { + + + list ALARM_STATS_LIST { + + key "id"; + leaf id { + type enumeration { + enum state; + } + description + "Table identifier value defined as state."; + } + + leaf alarms { + type uint64; + description + "Total alarms in the system."; + } + + leaf critical { + type uint64; + description + "Total critical alarms in the system."; + } + + leaf major { + type uint64; + description + "Total major alarms in the system."; + } + + leaf minor { + type uint64; + description + "Total minor alarms in the system."; + } + + leaf warning { + type uint64; + description + "Total warnings in the system."; + } + + leaf acknowledged { + type uint64; + description + "Total acknowledged alarms in the system."; + } + + } + } + } + + rpc acknowledge-alarms { + description + "Acknowledge an alarm on the system."; + + input { + leaf-list id { + type string; + description + "Alarm identifier."; + } + } + output { + leaf status { + type int32; + description + "The status of the operation execution request."; + } + leaf status-detail { + type string; + description + "The detailed status of the operation execution request."; + } + } + } + + rpc unacknowledge-alarms { + description + "Un-acknowledge an alarm on the system."; + + input { + leaf-list id { + type string; + description + "Alarm identifier."; + } + } + output { + leaf status { + type int32; + description + "The status of the operation execution request."; + } + leaf status-detail { + type string; + description + "The detailed status of the operation execution request."; + } + } + } + + + rpc show-alarms { + description + "Get current alarms based on given filter."; + + input { + choice option { + case time { + description + "Filter alarms on given time duration."; + container time { + leaf begin { + type yang-types:date-and-time; + description + "Specify the start time in RFC3339 format (UTC). + For example: 2021-05-12T13:45:45.455Z"; + } + leaf end { + type yang-types:date-and-time; + description + "Specify end time in RFC3339 format (UTC). + For example: 2021-05-12T13:45:45.455Z"; + } + } + } + + case last-interval { + description + "Filter alarms based on last interval."; + leaf interval { + type enumeration { + enum 5_MINUTES; + enum 60_MINUTES; + enum 24_HOURS; + } + description + "Last interval period."; + } + } + + case severity { + description + "Filter events based on severity."; + leaf severity { + type event:severity-type; + description + "Alarm severity"; + } + } + + case id { + description + "Filter alarms on unique ID."; + container id { + leaf begin { + type string; + description + "Begin identifier."; + } + leaf end { + type string; + description + "End identifier."; + } + } + } + } + } + + output { + leaf status { + type int32; + description + "Status of the operation execution request."; + } + leaf status-detail { + type string; + description + "Status details of the operation execution request."; + } + container ALARM { + list ALARM_LIST { + key "id"; + uses alarm-state; + } + } + } + } + } diff --git a/models/yang/sonic/sonic-event.yang b/models/yang/sonic/sonic-event.yang new file mode 100644 index 000000000..96d683680 --- /dev/null +++ b/models/yang/sonic/sonic-event.yang @@ -0,0 +1,232 @@ + module sonic-event { + namespace "http://github.com/Azure/sonic-events"; + prefix sevents; + yang-version 1.1; + + import ietf-yang-types { + prefix yang-types; + } + + // meta + organization + "SONiC"; + + contact + "SONiC"; + + description + "This module defines operational state data for SONiC events."; + + revision "2021-01-15" { + description + "Initial revision."; + } + + typedef timeticks64 { + type uint64; + description + "Time represented as ticks with 64-bit width. + This value is expressed as nanoseconds since the Unix Epoch."; + } + + typedef severity-type { + type enumeration { + enum CRITICAL; + enum MAJOR; + enum MINOR; + enum WARNING; + enum INFORMATIONAL; + } + description + "Severity of a raised condition."; + } + + typedef action-type { + type enumeration { + enum RAISE; + enum CLEAR; + enum ACKNOWLEDGE; + enum UNACKNOWLEDGE; + } + description + "Action on a raised condition."; + } + + grouping event-state { + + leaf id { + type string; + description "Sequence identifier for events."; + } + + leaf resource { + type string; + description "The item in the device that raised the event."; + } + + leaf text { + type string; + description "Dynamic message raised with the event."; + } + + leaf time-created { + type timeticks64; + description + "The time at which the event was raised by the system. + This value is expressed date-and-time format."; + } + + leaf type-id { + type string; + description "Type of event raised by the device."; + } + + leaf severity { + type severity-type; + description + "Severity of the event."; + } + + leaf action { + type action-type; + description "Action on the event."; + } + } + + container sonic-event { + + container EVENT { + + list EVENT_LIST { + key "id"; + uses event-state; + } + } + + container EVENT_STATS { + + + list EVENT_STATS_LIST { + + key "id"; + leaf id { + type enumeration { + enum state; + } + description + "Table identifier value defined as state."; + } + + leaf events { + type uint64; + description + "Total number of events in the system store."; + } + + leaf raised { + type uint64; + description + "Total number of events for raise operation in system store."; + } + + leaf acked { + type uint64; + description + "Total number of events for ack operation in system store."; + } + + leaf cleared { + type uint64; + description + "Total number of events for clear operation in system store."; + } + } + } + } + + + rpc show-events { + description + "Get events based on given filter."; + + input { + choice option { + case time { + description + "Filter events based on the time duration"; + container time { + leaf begin { + type yang-types:date-and-time; + description + "Specify the start time in RFC3339 format (UTC). + For example: 2021-05-12T13:45:45.455Z"; + } + leaf end { + type yang-types:date-and-time; + description + "Specify end time in RFC3339 format (UTC). + For example: 2021-05-12T13:45:45.455Z"; + } + } + } + case last-interval { + description + "Filter events on the last interval."; + leaf interval { + type enumeration { + enum 5_MINUTES; + enum 60_MINUTES; + enum 24_HOURS; + } + description + "Last interval period."; + } + } + case severity { + description + "Filter events based on severity."; + leaf severity { + type severity-type; + description + "Event severity"; + } + } + case id { + description + "Filter events on unique ID."; + container id { + leaf begin { + type string; + description + "Begin identifier."; + } + leaf end { + type string; + description + "End identifier."; + } + } + } + } + } + + output { + leaf status { + type int32; + description + "Status of the operation execution request."; + } + leaf status-detail { + type string; + description + "Status details of the operation execution request."; + } + container EVENT { + list EVENT_LIST { + key "id"; + uses event-state; + } + } + } + } + } diff --git a/models/yang/sonic/sonic-evprofile.yang b/models/yang/sonic/sonic-evprofile.yang new file mode 100644 index 000000000..3c6a09e5c --- /dev/null +++ b/models/yang/sonic/sonic-evprofile.yang @@ -0,0 +1,53 @@ +module sonic-evprofile { + namespace "http://github.com/Azure/sonic-evprofile"; + prefix evprofile; + yang-version 1.1; + + organization + "SONiC"; + + contact + "SONiC"; + + description + "SONiC yang for Event Profile."; + + revision 2021-05-12 { + description + "Initial revision."; + } + + rpc get-evprofile { + description "RPC for getting active event profile."; + + output { + leaf file-name { + type string; + description "Active Event Profile"; + } + leaf-list file-list { + type string; + description "List of Event Profiles on the device"; + } + } + } + + rpc set-evprofile { + description "RPC for applying specified event profile."; + + input { + leaf file-name { + type string; + description "Enter the name of the Event Profile file with/without '.json' suffix. To select default event profile, just enter 'default'"; + } + } + + output { + leaf status { + type string; + description "Event Profile validation status"; + } + } + } +} + diff --git a/translib/db/db.go b/translib/db/db.go index 6f1a1d550..2c9f8b587 100644 --- a/translib/db/db.go +++ b/translib/db/db.go @@ -142,6 +142,7 @@ const ( SnmpDB // 7 ErrorDB // 8 UserDB // 9 + EventDB // 10 // All DBs added above this line, please ---- MaxDB // The Number of DBs ) @@ -321,10 +322,39 @@ func getDBInstName (dbNo DBNum) string { return "ERROR_DB" case UserDB: return "USER_DB" + case EventDB: + return "EVENT_DB" } return "" } +func GetdbNameToIndex(dbName string) DBNum { + dbIndex := ConfigDB + switch dbName { + case "APPL_DB" : + dbIndex = ApplDB + case "ASIC_DB" : + dbIndex = AsicDB + case "COUNTERS_DB" : + dbIndex = CountersDB + case "LOGLEVEL_DB" : + dbIndex = LogLevelDB + case "CONFIG_DB" : + dbIndex = ConfigDB + case "FLEX_COUNTER_DB" : + dbIndex = FlexCounterDB + case "STATE_DB" : + dbIndex = StateDB + case "ERROR_DB" : + dbIndex = ErrorDB + case "USER_DB" : + dbIndex = UserDB + case "EVENT_DB" : + dbIndex = EventDB + } + return dbIndex +} + // NewDB is the factory method to create new DB's. func NewDB(opt Options) (*DB, error) { diff --git a/translib/transformer/xfmr_event.go b/translib/transformer/xfmr_event.go new file mode 100644 index 000000000..fe44b5de1 --- /dev/null +++ b/translib/transformer/xfmr_event.go @@ -0,0 +1,398 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright 2021 Dell, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +////////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "encoding/json" + "errors" + "math" + "strconv" + "strings" + "time" + + "github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Azure/sonic-mgmt-common/translib/utils" + log "github.com/golang/glog" +) + +func init() { + XlateFuncBind("DbToYang_event_severity_xfmr", DbToYang_event_severity_xfmr) + XlateFuncBind("DbToYang_alarm_severity_xfmr", DbToYang_alarm_severity_xfmr) + XlateFuncBind("rpc_unacknowledge_alarms", rpc_unacknowledge_alarms) + XlateFuncBind("rpc_acknowledge_alarms", rpc_acknowledge_alarms) + XlateFuncBind("rpc_get_events", rpc_get_events) + XlateFuncBind("rpc_get_alarms", rpc_get_alarms) + XlateFuncBind("YangToDb_event_stats_key_xfmr", YangToDb_event_stats_key_xfmr) + XlateFuncBind("YangToDb_alarm_stats_key_xfmr", YangToDb_alarm_stats_key_xfmr) +} + +var YangToDb_event_stats_key_xfmr KeyXfmrYangToDb = func(inParams XfmrParams) (string, error) { + return "state", nil +} + +var YangToDb_alarm_stats_key_xfmr KeyXfmrYangToDb = func(inParams XfmrParams) (string, error) { + return "state", nil +} + +func severity_xfmr(inParams XfmrParams, tbl string) (map[string]interface{}, error) { + var err error + result := make(map[string]interface{}) + data := (*inParams.dbDataMap)[inParams.curDb] + pTbl := data[tbl] + if _, ok := pTbl[inParams.key]; !ok { + return result, err + } + + pRec := pTbl[inParams.key] + sev, ok := pRec.Field["severity"] + if ok { + result["severity"] = "openconfig-alarm-types:" + sev + } + return result, err +} + +var DbToYang_event_severity_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + return severity_xfmr(inParams, "EVENT") +} + +var DbToYang_alarm_severity_xfmr FieldXfmrDbtoYang = func(inParams XfmrParams) (map[string]interface{}, error) { + return severity_xfmr(inParams, "ALARM") +} + +func get_events_with_filter_time(d *db.DB, tableName string, begin time.Time, end time.Time) ([]interface{}, error) { + var events []interface{} + + log.Info(" Begin time: ", begin.UnixNano(), ", End time:", end.UnixNano()) + log.Infof("Begin time: %s, End time: %s", begin, end) + + table, err := d.GetTable(&db.TableSpec{Name: tableName}) + + if err != nil { + log.Error("Reading event Table failed.", err) + return events, err + } + var keys []db.Key + keys, err = table.GetKeys() + + if err != nil { + log.Error("Get event table keys failed.", err) + return events, err + } + + for _, key := range keys { + if entry, err := table.GetEntry(key); err == nil { + + var time_created string + var ok bool + if time_created, ok = entry.Field["time-created"]; !ok { + continue + } + + t_nano, _ := strconv.ParseInt(time_created, 10, 64) + if t_nano <= end.UnixNano() && t_nano >= begin.UnixNano() { + events = append(events, entry.Field) + } + } + } + + return events, err +} + +func get_events_with_filter_id(d *db.DB, tableName string, begin string, end string) ([]interface{}, error) { + var begin_id, end_id uint64 + var events []interface{} + var keys []db.Key + + table, err := d.GetTable(&db.TableSpec{Name: tableName}) + + if err != nil { + log.Error("Reading the table failed.", err) + return events, err + } + + keys, err = table.GetKeys() + + if err != nil { + log.Error("Get event table keys failed.", err) + return events, err + } + + if end_id, err = strconv.ParseUint(end, 10, 64); err != nil { + end_id = math.MaxUint64 + } + + if begin_id, err = strconv.ParseUint(begin, 10, 64); err != nil { + begin_id = 0 + } + + for _, key := range keys { + if entry, err := table.GetEntry(key); err == nil { + var id_str string + var ok bool + if id_str, ok = entry.Field["id"]; !ok { + continue + } + id, _ := strconv.ParseUint(id_str, 10, 64) + + if id <= end_id && id >= begin_id { + events = append(events, entry.Field) + } + } + } + return events, err +} + +func get_events_with_filter_severity(d *db.DB, tableName string, severity string) ([]interface{}, error) { + var events []interface{} + table, err := d.GetTable(&db.TableSpec{Name: tableName}) + + if err != nil { + log.Error("Reading the table failed.", err) + return events, err + } + var keys []db.Key + keys, err = table.GetKeys() + + if err != nil { + log.Error("Get event table keys failed.", err) + return events, err + } + + for _, key := range keys { + if entry, err := table.GetEntry(key); err == nil { + var ev_sev string + var ok bool + if ev_sev, ok = entry.Field["severity"]; !ok { + continue + } + entry.Field["id"] = key.Comp[0] + if strings.EqualFold(ev_sev, severity) { + events = append(events, entry.Field) + } + } + } + return events, err +} + +func process_show_request(dbs [db.MaxDB]*db.DB, table string, input interface{}) ([]interface{}, error) { + var err error + var events []interface{} + + mapData := input.(map[string]interface{}) + + if id, ok := mapData["id"].(interface{}); ok { + idMap := id.(map[string]interface{}) + var begin, end string + if begin, ok = idMap["begin"].(string); !ok { + begin = "" + } + + if end, ok = idMap["end"].(string); !ok { + end = "" + } + log.Info("begin ", begin, " :end ", end) + events, err = get_events_with_filter_id(dbs[db.EventDB], table, begin, end) + + } else if interval, ok := mapData["interval"].(string); ok { + end_time := time.Now() + begin_time := time.Now() + is_interval_valid := true + if interval == "5_MINUTES" { + begin_time = begin_time.Add(-time.Minute * 5) + } else if interval == "60_MINUTES" { + begin_time = begin_time.Add(-time.Minute * 60) + } else if interval == "24_HOURS" { + begin_time = begin_time.Add(-time.Hour * 24) + } else { + is_interval_valid = false + log.Info("Error interval time ", interval) + err = errors.New("Invalid interval") + } + if is_interval_valid { + events, err = get_events_with_filter_time(dbs[db.EventDB], table, begin_time, end_time) + } + + } else if time_filter, ok := mapData["time"].(interface{}); ok { + + timeMap := time_filter.(map[string]interface{}) + + begin := time.Time{} + end := time.Now() + + if begin_time, ok := timeMap["begin"].(string); ok { + begin, err = time.Parse(time.RFC3339, begin_time) + if err != nil { + log.Info("Error parsing begin time ", begin_time) + } + } + + if end_time, ok := timeMap["end"].(string); ok { + end, err = time.Parse(time.RFC3339, end_time) + if err != nil { + log.Info("Error parsing end time ", end_time) + } + } + events, err = get_events_with_filter_time(dbs[db.EventDB], table, begin, end) + } else if severity, ok := mapData["severity"].(string); ok { + + events, err = get_events_with_filter_severity(dbs[db.EventDB], table, severity) + } + return events, err +} + +var rpc_get_events RpcCallpoint = func(body []byte, dbs [db.MaxDB]*db.DB) ([]byte, error) { + + var mapData map[string]interface{} + var events []interface{} + + err := json.Unmarshal(body, &mapData) + if err != nil { + log.Info("Failed to unmarshall given input data") + return nil, err + } + + var result struct { + Output struct { + Status int32 `json:"status"` + Status_detail string `json:"status-detail"` + EVENT struct { + EVENT_LIST []interface{} `json:"EVENT_LIST"` + } `json:"EVENT"` + } `json:"sonic-event:output"` + } + + log.Info("rpc_get_events map_data: ", mapData) + + input := mapData["sonic-event:input"] + events, err = process_show_request(dbs, "EVENT", input) + if err == nil { + result.Output.Status = 0 + result.Output.EVENT.EVENT_LIST = events + } else { + result.Output.Status = 1 + result.Output.Status_detail = string(err.Error()) + } + + return json.Marshal(&result) + +} + +var rpc_get_alarms RpcCallpoint = func(body []byte, dbs [db.MaxDB]*db.DB) ([]byte, error) { + + var mapData map[string]interface{} + var alarms []interface{} + + err := json.Unmarshal(body, &mapData) + if err != nil { + log.Info("Failed to unmarshall given input data") + return nil, err + } + + var result struct { + Output struct { + Status int32 `json:"status"` + Status_detail string `json:"status-detail"` + ALARM struct { + ALARM_LIST []interface{} `json:"ALARM_LIST"` + } `json:"ALARM"` + } `json:"sonic-alarm:output"` + } + + log.Info("rpc_get_alarms map_data: ", mapData) + + input := mapData["sonic-alarm:input"] + + alarms, err = process_show_request(dbs, "ALARM", input) + if err == nil { + result.Output.Status = 0 + result.Output.ALARM.ALARM_LIST = alarms + } else { + result.Output.Status = 1 + result.Output.Status_detail = string(err.Error()) + } + + return json.Marshal(&result) +} + +func ack_alarm(body []byte, dbs [db.MaxDB]*db.DB, op string) ([]byte, error) { + + var mapData map[string]interface{} + + err := json.Unmarshal(body, &mapData) + if err != nil { + log.Info("Failed to unmarshal given input data") + return nil, err + } + + var result struct { + Output struct { + Status int32 `json:"status"` + Status_detail string `json:"status-detail"` + } `json:"sonic-alarm:output"` + } + + log.Infof("In ack_alarm operation %s, input %s ", op, mapData) + + input := mapData["sonic-alarm:input"] + + mapData = input.(map[string]interface{}) + error_ids := "" + + action := utils.UNACKNOWLEDGE + if op == "true" { + action = utils.ACKNOWLEDGE + } + + if ids, ok := mapData["id"].([]interface{}); ok { + for _, id := range ids { + + if _, err = strconv.ParseInt(id.(string), 10, 64); err == nil { + err = utils.EventNotify(dbs, "", id.(string), action, "Alarm id %s %s.", id.(string), utils.GetActionStr(action)) + } + + if err != nil { + log.Info("Unable to write ack mode to alarm id ", id.(string)) + error_ids += id.(string) + error_ids += "," + } + } + error_ids = strings.TrimSuffix(error_ids, ",") + if error_ids != "" { + result.Output.Status = 1 + op_str := "ack " + if op == "false" { + op_str = "unack " + } + result.Output.Status_detail = "Failed to " + op_str + " " + error_ids + } else { + result.Output.Status = 0 + } + } + return json.Marshal(&result) +} + +var rpc_acknowledge_alarms RpcCallpoint = func(body []byte, dbs [db.MaxDB]*db.DB) ([]byte, error) { + + return ack_alarm(body, dbs, "true") +} + +var rpc_unacknowledge_alarms RpcCallpoint = func(body []byte, dbs [db.MaxDB]*db.DB) ([]byte, error) { + + return ack_alarm(body, dbs, "false") +} diff --git a/translib/transformer/xfmr_evprofile.go b/translib/transformer/xfmr_evprofile.go new file mode 100644 index 000000000..1275aa175 --- /dev/null +++ b/translib/transformer/xfmr_evprofile.go @@ -0,0 +1,206 @@ +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright 2019 Dell, Inc. // +// // +// Licensed under the Apache License, Version 2.0 (the "License"); // +// you may not use this file except in compliance with the License. // +// You may obtain a copy of the License at // +// // +// http://www.apache.org/licenses/LICENSE-2.0 // +// // +// Unless required by applicable law or agreed to in writing, software // +// distributed under the License is distributed on an "AS IS" BASIS, // +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // +// See the License for the specific language governing permissions and // +// limitations under the License. // +// // +//////////////////////////////////////////////////////////////////////////////// + +package transformer + +import ( + "encoding/json" + "errors" + "io/ioutil" + "os" + "strings" + + "github.com/Azure/sonic-mgmt-common/translib/db" + log "github.com/golang/glog" +) + +const DEFAULT_EVPROFILE_DIR = "/etc/evprofile/" +const DEFAULT_EVPROFILE_NAME = "default.json" +const DEFAULT_EVPROFILE_PATH = DEFAULT_EVPROFILE_DIR + DEFAULT_EVPROFILE_NAME +const ACTIVE_EVPROFILE_SYMLINK_NAME = ".current" +const ACTIVE_EVPROFILE_SYMLINK_PATH = DEFAULT_EVPROFILE_DIR + ACTIVE_EVPROFILE_SYMLINK_NAME + +// Event struct which contains all the fields +type Event struct { + Name string `json:"name"` + Severity string `json:"severity"` + Enable string `json:"enable"` + Message string `json:"message"` +} + +// Events struct which contains array of Event +type Events struct { + Events []Event `json:"events"` +} + +var ev_severity = []string{"informational", "warning", "minor", "major", "critical"} +var ev_enable = []string{"true", "false"} + +func init() { + XlateFuncBind("rpc_getevprofile_cb", rpc_getevprofile_cb) + XlateFuncBind("rpc_setevprofile_cb", rpc_setevprofile_cb) +} + +func validate_evprofile(ev_sev string, ev_en string) bool { + for _, en_item := range ev_enable { + if strings.EqualFold(en_item, ev_en) { + for _, sev_item := range ev_severity { + if strings.EqualFold(sev_item, ev_sev) { + return true + } + } + log.Errorf("Invalid value for severity field %s", ev_sev) + return false + } + } + log.Errorf("Invalid value for enable field %s", ev_en) + return false +} + +var rpc_setevprofile_cb RpcCallpoint = func(body []byte, dbs [db.MaxDB]*db.DB) ([]byte, error) { + var setevprofile struct { + Output struct { + Result string `json:"status"` + } `json:"sonic-set-evprofile:output"` + } + + s := string(body) + log.Info("received body", s) + + var inputData map[string]interface{} + err := json.Unmarshal(body, &inputData) + if err != nil { + return nil, err + } + + var filePath string + var db_field [2]string + + db_field[0] = "name" + + input := inputData["sonic-evprofile:input"] + if input != nil { + inputData = input.(map[string]interface{}) + if value, ok := inputData["file-name"].(string); ok { + if value != "" { + filePath = DEFAULT_EVPROFILE_DIR + value + db_field[1] = value + } else { + filePath = DEFAULT_EVPROFILE_PATH + db_field[1] = DEFAULT_EVPROFILE_NAME + } + } + } + + log.Info("input file path ", filePath) + + if _, err := os.Lstat(filePath); err != nil { + log.Error("Event Profile doesnt exist") + setevprofile.Output.Result = "Event Profile doesnt exist" + result, _ := json.Marshal(&setevprofile) + return result, err + } + + // Open jsonFile + jsonFile, _ := os.Open(filePath) + + // read opened jsonFile as a byte array. + byteValue, _ := ioutil.ReadAll(jsonFile) + + // initialize Events array + var events Events + + // unmarshal byteArray which contains jsonFile's content into 'events' + json.Unmarshal(byteValue, &events) + + // iterate through every event within events array + for i := 0; i < len(events.Events); i++ { + log.Info("Event Name: " + events.Events[i].Name) + log.Info("Event Severity: " + events.Events[i].Severity) + log.Info("Event Enable: " + events.Events[i].Enable) + log.Info("Event message: " + events.Events[i].Message) + if !validate_evprofile(events.Events[i].Severity, events.Events[i].Enable) { + setevprofile.Output.Result = "Event Profile has invalid values for parameters" + result, _ := json.Marshal(&setevprofile) + defer jsonFile.Close() + return result, errors.New("Event Profile has invalid values for parameters") + } + } + + d := dbs[db.EventDB] + key := "evprofile" + filename_field := db.Value{Field: make(map[string]string)} + filename_field.Set(db_field[0], db_field[1]) + + existingEntry, _ := d.GetEntry(&db.TableSpec{Name: "EVPROFILE_TABLE"}, db.Key{Comp: []string{key}}) + if existingEntry.IsPopulated() { + log.Info("EVPROFILE table is populdated. Modifying the filename") + err = d.ModEntry(&db.TableSpec{Name: "EVPROFILE_TABLE"}, db.Key{Comp: []string{key}}, filename_field) + } else { + log.Info("EVPROFILE table is empty. Creating the filename entry..") + err = d.CreateEntry(&db.TableSpec{Name: "EVPROFILE_TABLE"}, db.Key{Comp: []string{key}}, filename_field) + } + + if err != nil { + log.Error("Unable to announce validated event profile name to backend") + setevprofile.Output.Result = "Event Profile can not be processed." + result, _ := json.Marshal(&setevprofile) + defer jsonFile.Close() + return result, err + } + + setevprofile.Output.Result = "Event Profile is applied." + result, _ := json.Marshal(&setevprofile) + defer jsonFile.Close() + return result, nil +} + +var rpc_getevprofile_cb RpcCallpoint = func(body []byte, dbs [db.MaxDB]*db.DB) ([]byte, error) { + + var getevprofile struct { + Output struct { + Result string `json:"file-name"` + Filelist []string `json:"file-list"` + } `json:"sonic-get-evprofile:output"` + } + + // read symlink + n, e := os.Readlink(ACTIVE_EVPROFILE_SYMLINK_PATH) + if e != nil { + getevprofile.Output.Result = "No event profile is active." + } else { + getevprofile.Output.Result = string(n[15:]) + } + + // read all the files under evprofile directory + files, err := ioutil.ReadDir(DEFAULT_EVPROFILE_DIR) + if err == nil { + for _, file := range files { + if file.Name() != ACTIVE_EVPROFILE_SYMLINK_NAME { + getevprofile.Output.Filelist = append(getevprofile.Output.Filelist, file.Name()) + } + } + } else { + getevprofile.Output.Filelist = append(getevprofile.Output.Filelist, "Can not fetch Event Profile list.") + log.Error("Can not fetch Event Profile list.") + } + + result, _ := json.Marshal(&getevprofile) + + return result, nil +} diff --git a/translib/transformer/xlate_utils.go b/translib/transformer/xlate_utils.go index 650ff9d7f..10c824835 100644 --- a/translib/transformer/xlate_utils.go +++ b/translib/transformer/xlate_utils.go @@ -532,7 +532,7 @@ func getDBOptions(dbNo db.DBNum) db.Options { switch dbNo { case db.ApplDB, db.CountersDB: opt = getDBOptionsWithSeparator(dbNo, "", ":", ":") - case db.FlexCounterDB, db.AsicDB, db.LogLevelDB, db.ConfigDB, db.StateDB, db.ErrorDB, db.UserDB: + case db.FlexCounterDB, db.AsicDB, db.LogLevelDB, db.ConfigDB, db.StateDB, db.ErrorDB, db.UserDB, db.EventDB: opt = getDBOptionsWithSeparator(dbNo, "", "|", "|") } diff --git a/translib/transformer/xspec.go b/translib/transformer/xspec.go index ae61ad297..35140094b 100644 --- a/translib/transformer/xspec.go +++ b/translib/transformer/xspec.go @@ -519,7 +519,7 @@ func dbMapFill(tableName string, curPath string, moduleNm string, xDbSpecMap map case "rpc-callback" : xDbSpecMap[dbXpath].rpcFunc = ext.NName() case "db-name" : - xDbSpecMap[dbXpath].dbIndex = dbNameToIndex(ext.NName()) + xDbSpecMap[dbXpath].dbIndex = db.GetdbNameToIndex(ext.NName()) case "key-delim" : xDbSpecMap[dbXpath].delim = ext.NName() default : @@ -670,31 +670,6 @@ func childToUpdateParent( xpath string, tableName string) { childToUpdateParent(parent, tableName) } -func dbNameToIndex(dbName string) db.DBNum { - dbIndex := db.ConfigDB - switch dbName { - case "APPL_DB" : - dbIndex = db.ApplDB - case "ASIC_DB" : - dbIndex = db.AsicDB - case "COUNTERS_DB" : - dbIndex = db.CountersDB - case "LOGLEVEL_DB" : - dbIndex = db.LogLevelDB - case "CONFIG_DB" : - dbIndex = db.ConfigDB - case "FLEX_COUNTER_DB" : - dbIndex = db.FlexCounterDB - case "STATE_DB" : - dbIndex = db.StateDB - case "ERROR_DB" : - dbIndex = db.ErrorDB - case "USER_DB" : - dbIndex = db.UserDB - } - return dbIndex -} - /* Build lookup map based on yang xpath */ func annotEntryFill(xYangSpecMap map[string]*yangXpathInfo, xpath string, entry *yang.Entry) { xpathData := new(yangXpathInfo) @@ -746,7 +721,7 @@ func annotEntryFill(xYangSpecMap map[string]*yangXpathInfo, xpath string, entry case "use-self-key" : xpathData.keyXpath = nil case "db-name" : - xpathData.dbIndex = dbNameToIndex(ext.NName()) + xpathData.dbIndex = db.GetdbNameToIndex(ext.NName()) case "table-owner" : if xpathData.tblOwner == nil { xpathData.tblOwner = new(bool) @@ -890,7 +865,7 @@ func annotDbSpecMapFill(xDbSpecMap map[string]*dbInfo, dbXpath string, entry *ya } *dbXpathData.keyName = ext.NName() case "db-name" : - dbXpathData.dbIndex = dbNameToIndex(ext.NName()) + dbXpathData.dbIndex = db.GetdbNameToIndex(ext.NName()) case "value-transformer" : fieldName := pname[len(pname) - 1] fieldXpath := tableName+"/"+fieldName diff --git a/translib/translib.go b/translib/translib.go index 737d530fa..3919ab777 100644 --- a/translib/translib.go +++ b/translib/translib.go @@ -1091,6 +1091,14 @@ func getAllDbs(isGetCase bool) ([db.MaxDB]*db.DB, error) { return dbs, err } + //Create Event DB connection + dbs[db.EventDB], err = db.NewDB(getDBOptions(db.EventDB, isWriteDisabled)) + + if err != nil { + closeAllDbs(dbs[:]) + return dbs, err + } + return dbs, err } @@ -1121,7 +1129,7 @@ func getDBOptions(dbNo db.DBNum, isWriteDisabled bool) db.Options { switch dbNo { case db.ApplDB, db.CountersDB, db.AsicDB: opt = getDBOptionsWithSeparator(dbNo, "", ":", ":", isWriteDisabled) - case db.FlexCounterDB, db.LogLevelDB, db.ConfigDB, db.StateDB: + case db.FlexCounterDB, db.LogLevelDB, db.ConfigDB, db.StateDB, db.EventDB: opt = getDBOptionsWithSeparator(dbNo, "", "|", "|", isWriteDisabled) } diff --git a/translib/utils/utils.go b/translib/utils/utils.go index 466c618bf..afbe90e0a 100644 --- a/translib/utils/utils.go +++ b/translib/utils/utils.go @@ -21,9 +21,11 @@ package utils import ( - //"github.com/Azure/sonic-mgmt-common/translib/db" + "github.com/Azure/sonic-mgmt-common/translib/db" "github.com/Azure/sonic-mgmt-common/cvl" "fmt" + "time" + "strconv" log "github.com/golang/glog" ) @@ -67,5 +69,59 @@ func RemoveElement(sl []string, str string) []string { return sl } +func GetActionStr(action int) string { + switch action { + case CLEAR: + return "CLEAR" + case RAISE: + return "RAISE" + case ACKNOWLEDGE: + return "ACKNOWLEDGE" + case UNACKNOWLEDGE: + return "UNACKNOWLEDGE" + default: + return "" + } +} + +var seq_id = 1 + +const ( + RAISE = iota + CLEAR + ACKNOWLEDGE + UNACKNOWLEDGE +) + +func EventNotify(dbs [db.MaxDB]*db.DB, name string, source string, action int, format string, a ...string) error { + + timeCreated := time.Now().UnixNano() + seq_id++ + tblKey := name + strconv.Itoa(seq_id) + + s := make([]interface{}, len(a)) + for i, v := range a { + s[i] = v + } + + text := fmt.Sprintf(format, s...) + evtFields := db.Value{Field: make(map[string]string)} + evtFields.Set("type-id", name) + evtFields.Set("text", text) + evtFields.Set("action", GetActionStr(action)) + evtFields.Set("time-created", strconv.FormatInt(timeCreated, 10)) + evtFields.Set("reckey", tblKey) + evtFields.Set("resource", source) + + d := dbs[db.EventDB] + + err := d.CreateEntry(&db.TableSpec{Name: "EVENTPUBSUB"}, db.Key{Comp: []string{tblKey}}, evtFields) + if err != nil { + log.Info("Unable to write event ", tblKey) + } + return err +} + +