# platform project (RICP).
#==================================================================================
-FROM nexus3.o-ran-sc.org:10004/o-ran-sc/bldr-ubuntu18-c-go:8-u18.04 as ubuntu-alarmadapter
+FROM nexus3.o-ran-sc.org:10004/o-ran-sc/bldr-ubuntu18-c-go:9-u18.04 as ubuntu-alarmadapter
# Install utilities
RUN apt update && apt install -y iputils-ping net-tools curl sudo
import (
"encoding/json"
"fmt"
- "sync"
"time"
clientruntime "github.com/go-openapi/runtime/client"
app "gerrit.o-ran-sc.org/r/ric-plt/xapp-frame/pkg/xapp"
)
-type AlertStatus string
-
-const (
- AlertStatusActive = "active"
- AlertStatusResolved = "resolved"
-)
-
-type AlarmAdapter struct {
- amHost string
- amBaseUrl string
- amSchemes []string
- alertInterval int
- activeAlarms []alarm.Alarm
- mutex sync.Mutex
- rmrReady bool
- postClear bool
-}
-
-var Version string
-var Hash string
-
-// Main function
-func main() {
- NewAlarmAdapter("", 0).Run(true)
-}
-
-func NewAlarmAdapter(amHost string, alertInterval int) *AlarmAdapter {
- if alertInterval == 0 {
- alertInterval = viper.GetInt("promAlertManager.alertInterval")
- }
-
- if amHost == "" {
- amHost = viper.GetString("promAlertManager.address")
- }
-
- return &AlarmAdapter{
- rmrReady: false,
- amHost: amHost,
- amBaseUrl: viper.GetString("promAlertManager.baseUrl"),
- amSchemes: []string{viper.GetString("promAlertManager.schemes")},
- alertInterval: alertInterval,
- activeAlarms: make([]alarm.Alarm, 0),
- }
-}
-
-func (a *AlarmAdapter) Run(sdlcheck bool) {
- app.Logger.SetMdc("alarmAdapter", fmt.Sprintf("%s:%s", Version, Hash))
- app.SetReadyCB(func(d interface{}) { a.rmrReady = true }, true)
- app.Resource.InjectStatusCb(a.StatusCB)
-
- app.Resource.InjectRoute("/ric/v1/alarms", a.GetActiveAlarms, "GET")
- app.Resource.InjectRoute("/ric/v1/alarms", a.RaiseAlarm, "POST")
- app.Resource.InjectRoute("/ric/v1/alarms", a.ClearAlarm, "DELETE")
-
- // Start background timer for re-raising alerts
- a.postClear = sdlcheck
- go a.StartAlertTimer()
-
- app.RunWithParams(a, sdlcheck)
-}
-
func (a *AlarmAdapter) StartAlertTimer() {
tick := time.Tick(time.Duration(a.alertInterval) * time.Millisecond)
for range tick {
}
app.Logger.Info("newAlarm: %v", m)
+ return a.ProcessAlarm(&m)
+}
+
+func (a *AlarmAdapter) ProcessAlarm(m *alarm.AlarmMessage) (*alert.PostAlertsOK, error) {
if _, ok := alarm.RICAlarmDefinitions[m.Alarm.SpecificProblem]; !ok {
- app.Logger.Warn("Alarm (SP='%d') not recognized, ignoring ...", m.Alarm.SpecificProblem)
+ app.Logger.Warn("Alarm (SP='%d') not recognized, suppressing ...", m.Alarm.SpecificProblem)
return nil, nil
}
// Suppress duplicate alarms
idx, found := a.IsMatchFound(m.Alarm)
if found && m.AlarmAction != alarm.AlarmActionClear {
- app.Logger.Info("Duplicate alarm ... suppressing!")
+ app.Logger.Info("Duplicate alarm found, suppressing ...")
return nil, nil
}
// Clear alarm if found from active alarm list
if m.AlarmAction == alarm.AlarmActionClear {
if found {
- a.activeAlarms = a.RemoveAlarm(a.activeAlarms, idx)
- app.Logger.Info("Active alarm cleared!")
+ a.activeAlarms = a.RemoveAlarm(a.activeAlarms, idx, "active")
if a.postClear {
return a.PostAlert(a.GenerateAlertLabels(m.Alarm, AlertStatusResolved))
}
}
- app.Logger.Info("No matching alarm found, ignoring!")
+ app.Logger.Info("No matching active alarm found, suppressing ...")
return nil, nil
}
// New alarm -> update active alarms and post to Alert Manager
if m.AlarmAction == alarm.AlarmActionRaise {
- a.UpdateActiveAlarms(m.Alarm)
+ a.UpdateAlarmLists(m.Alarm)
return a.PostAlert(a.GenerateAlertLabels(m.Alarm, AlertStatusActive))
}
func (a *AlarmAdapter) IsMatchFound(newAlarm alarm.Alarm) (int, bool) {
for i, m := range a.activeAlarms {
if m.ManagedObjectId == newAlarm.ManagedObjectId && m.ApplicationId == newAlarm.ApplicationId &&
- m.SpecificProblem == newAlarm.SpecificProblem && m.IdentifyingInfo == newAlarm.IdentifyingInfo {
+ m.SpecificProblem == newAlarm.SpecificProblem && m.IdentifyingInfo == newAlarm.IdentifyingInfo &&
+ m.PerceivedSeverity == newAlarm.PerceivedSeverity {
return i, true
}
}
return -1, false
}
-func (a *AlarmAdapter) RemoveAlarm(alarms []alarm.Alarm, i int) []alarm.Alarm {
+func (a *AlarmAdapter) RemoveAlarm(alarms []alarm.Alarm, i int, listName string) []alarm.Alarm {
a.mutex.Lock()
defer a.mutex.Unlock()
+ app.Logger.Info("Alarm '%+v' deleted from the '%s' list", alarms[i], listName)
copy(alarms[i:], alarms[i+1:])
return alarms[:len(alarms)-1]
}
-func (a *AlarmAdapter) UpdateActiveAlarms(newAlarm alarm.Alarm) {
+func (a *AlarmAdapter) UpdateAlarmLists(newAlarm alarm.Alarm) {
a.mutex.Lock()
defer a.mutex.Unlock()
- // For now just keep the active alarms in-memory. Use SDL later
+ // If maximum number of active alarms is reached, purge the oldest alarm
+ if len(a.activeAlarms) >= viper.GetInt("controls.maxActiveAlarms") {
+ a.activeAlarms = a.RemoveAlarm(a.activeAlarms, 0, "active")
+ }
+
+ if len(a.alarmHistory) >= viper.GetInt("controls.maxAlarmHistory") {
+ a.alarmHistory = a.RemoveAlarm(a.alarmHistory, 0, "history")
+ }
+
+ // @todo: For now just keep the alarms (both active and history) in-memory. Use SDL later for persistence
a.activeAlarms = append(a.activeAlarms, newAlarm)
+ a.alarmHistory = append(a.alarmHistory, newAlarm)
}
func (a *AlarmAdapter) GenerateAlertLabels(newAlarm alarm.Alarm, status AlertStatus) (models.LabelSet, models.LabelSet) {
return a.rmrReady
}
+
+func (a *AlarmAdapter) Run(sdlcheck bool) {
+ app.Logger.SetMdc("alarmAdapter", fmt.Sprintf("%s:%s", Version, Hash))
+ app.SetReadyCB(func(d interface{}) { a.rmrReady = true }, true)
+ app.Resource.InjectStatusCb(a.StatusCB)
+
+ app.Resource.InjectRoute("/ric/v1/alarms", a.RaiseAlarm, "POST")
+ app.Resource.InjectRoute("/ric/v1/alarms", a.ClearAlarm, "DELETE")
+ app.Resource.InjectRoute("/ric/v1/alarms/active", a.GetActiveAlarms, "GET")
+ app.Resource.InjectRoute("/ric/v1/alarms/history", a.GetAlarmHistory, "GET")
+
+ // Start background timer for re-raising alerts
+ a.postClear = sdlcheck
+ go a.StartAlertTimer()
+
+ app.RunWithParams(a, sdlcheck)
+}
+
+func NewAlarmAdapter(amHost string, alertInterval int) *AlarmAdapter {
+ if alertInterval == 0 {
+ alertInterval = viper.GetInt("controls.promAlertManager.alertInterval")
+ }
+
+ if amHost == "" {
+ amHost = viper.GetString("controls.promAlertManager.address")
+ }
+
+ return &AlarmAdapter{
+ rmrReady: false,
+ amHost: amHost,
+ amBaseUrl: viper.GetString("controls.promAlertManager.baseUrl"),
+ amSchemes: []string{viper.GetString("controls.promAlertManager.schemes")},
+ alertInterval: alertInterval,
+ activeAlarms: make([]alarm.Alarm, 0),
+ alarmHistory: make([]alarm.Alarm, 0),
+ }
+}
+
+// Main function
+func main() {
+ NewAlarmAdapter("", 0).Run(true)
+}
// Test cases
func TestMain(M *testing.M) {
+ os.Setenv("ALARM_IF_RMR", "true")
alarmAdapter = NewAlarmAdapter("localhost:9093", 500)
go alarmAdapter.Run(false)
time.Sleep(time.Duration(2) * time.Second)
ts := CreatePromAlertSimulator(t, "POST", "/api/v2/alerts", http.StatusOK, models.LabelSet{})
defer ts.Close()
- a := alarmer.NewAlarm(alarm.RIC_RT_DISTRIBUTION_FAILED, alarm.SeverityMajor, "Some App data", "eth 0 1")
+ a := alarmer.NewAlarm(alarm.RIC_RT_DISTRIBUTION_FAILED, alarm.SeverityCritical, "Some App data", "eth 0 1")
assert.Nil(t, alarmer.Raise(a), "raise failed")
VerifyAlarm(t, a, 1)
defer ts.Close()
// Raise the alarm
- a := alarmer.NewAlarm(alarm.RIC_RT_DISTRIBUTION_FAILED, alarm.SeverityMajor, "Some App data", "eth 0 1")
+ a := alarmer.NewAlarm(alarm.RIC_RT_DISTRIBUTION_FAILED, alarm.SeverityCritical, "Some App data", "eth 0 1")
assert.Nil(t, alarmer.Raise(a), "raise failed")
VerifyAlarm(t, a, 1)
// Now Clear the alarm and check alarm is removed
- a = alarmer.NewAlarm(alarm.RIC_RT_DISTRIBUTION_FAILED, alarm.SeverityCleared, "Some App data", "eth 0 1")
+ a = alarmer.NewAlarm(alarm.RIC_RT_DISTRIBUTION_FAILED, alarm.SeverityCritical, "Some App data", "eth 0 1")
assert.Nil(t, alarmer.Clear(a), "clear failed")
time.Sleep(time.Duration(2) * time.Second)
import (
"encoding/json"
"net/http"
+ "time"
"gerrit.o-ran-sc.org/r/ric-plt/alarm-go/alarm"
app "gerrit.o-ran-sc.org/r/ric-plt/xapp-frame/pkg/xapp"
)
-var alarmClient *alarm.RICAlarm
+func (a *AlarmAdapter) respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(code)
+ if payload != nil {
+ response, _ := json.Marshal(payload)
+ w.Write(response)
+ }
+}
func (a *AlarmAdapter) GetActiveAlarms(w http.ResponseWriter, r *http.Request) {
- app.Logger.Info("GetActiveAlarms: request received!")
+ app.Logger.Info("GetActiveAlarms: %+v", a.activeAlarms)
+ a.respondWithJSON(w, http.StatusOK, a.activeAlarms)
+}
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(http.StatusOK)
- response, _ := json.Marshal(a.activeAlarms)
- w.Write(response)
+func (a *AlarmAdapter) GetAlarmHistory(w http.ResponseWriter, r *http.Request) {
+ app.Logger.Info("GetAlarmHistory: %+v", a.alarmHistory)
+ a.respondWithJSON(w, http.StatusOK, a.alarmHistory)
}
func (a *AlarmAdapter) RaiseAlarm(w http.ResponseWriter, r *http.Request) {
- a.doAction(w, r, true)
+ if err := a.doAction(w, r, true); err != nil {
+ a.respondWithJSON(w, http.StatusOK, err)
+ }
}
func (a *AlarmAdapter) ClearAlarm(w http.ResponseWriter, r *http.Request) {
- a.doAction(w, r, false)
+ if err := a.doAction(w, r, false); err != nil {
+ a.respondWithJSON(w, http.StatusOK, err)
+ }
}
-func (a *AlarmAdapter) doAction(w http.ResponseWriter, r *http.Request, raiseAlarm bool) {
- app.Logger.Info("doAction: request received!")
+func (a *AlarmAdapter) doAction(w http.ResponseWriter, r *http.Request, isRaiseAlarm bool) error {
+ app.Logger.Info("doAction: request received = %t", isRaiseAlarm)
if r.Body == nil {
- return
+ app.Logger.Error("Error: Invalid message body!")
+ return nil
}
defer r.Body.Close()
- var d alarm.Alarm
- err := json.NewDecoder(r.Body).Decode(&d)
- if err != nil {
+ var m alarm.AlarmMessage
+ if err := json.NewDecoder(r.Body).Decode(&m); err != nil {
app.Logger.Error("json.NewDecoder failed: %v", err)
- return
+ return err
}
- if alarmClient == nil {
- alarmClient, err = alarm.InitAlarm("RIC", "UEEC")
- if err != nil {
- app.Logger.Error("json.NewDecoder failed: %v", err)
- return
- }
+ if m.Alarm.ManagedObjectId == "" || m.Alarm.ApplicationId == "" || m.AlarmAction == "" {
+ app.Logger.Error("Error: Mandatory parameters missing!")
+ return nil
+ }
+
+ if m.AlarmTime == 0 {
+ m.AlarmTime = time.Now().UnixNano() / 1000
+ }
+
+ _, err := a.ProcessAlarm(&m)
+ return err
+}
+
+func (a *AlarmAdapter) HandleViaRmr(d alarm.Alarm, isRaiseAlarm bool) error {
+ alarmClient, err := alarm.InitAlarm(d.ManagedObjectId, d.ApplicationId)
+ if err != nil {
+ app.Logger.Error("json.NewDecoder failed: %v", err)
+ return err
}
alarmData := alarmClient.NewAlarm(d.SpecificProblem, d.PerceivedSeverity, d.AdditionalInfo, d.IdentifyingInfo)
- if raiseAlarm {
+ if isRaiseAlarm {
alarmClient.Raise(alarmData)
} else {
alarmClient.Clear(alarmData)
}
+
+ return nil
}
--- /dev/null
+/*
+ * Copyright (c) 2020 AT&T Intellectual Property.
+ * Copyright (c) 2020 Nokia.
+ *
+ * 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.
+ *
+ * This source code is part of the near-RT RIC (RAN Intelligent Controller)
+ * platform project (RICP).
+ */
+
+package main
+
+import (
+ "sync"
+
+ "gerrit.o-ran-sc.org/r/ric-plt/alarm-go/alarm"
+)
+
+type AlarmAdapter struct {
+ amHost string
+ amBaseUrl string
+ amSchemes []string
+ alertInterval int
+ activeAlarms []alarm.Alarm
+ alarmHistory []alarm.Alarm
+ mutex sync.Mutex
+ rmrReady bool
+ postClear bool
+}
+
+type AlertStatus string
+
+const (
+ AlertStatusActive = "active"
+ AlertStatusResolved = "resolved"
+)
+
+var Version string
+var Hash string
# By default this file is in the docker build directory,
# but the location can configured in the JJB template.
---
-tag: 0.4.5
+tag: 0.5.0
package alarm
import (
+ "bytes"
"encoding/json"
"errors"
"fmt"
+ "io/ioutil"
"log"
+ "net/http"
+ "os"
"time"
"unsafe"
- "os"
- "io/ioutil"
)
/*
// The identities are used when raising/clearing alarms, unless provided by the applications.
func InitAlarm(mo, id string) (*RICAlarm, error) {
r := &RICAlarm{
- moId: mo,
- appId: id,
+ moId: mo,
+ appId: id,
+ adapterUrl: ALARM_ADAPTER_HTTP_URL,
+ }
+
+ if os.Getenv("ALARM_ADAPTER_URL") != "" {
+ r.adapterUrl = os.Getenv("ALARM_ADAPTER_URL")
+ }
+
+ if os.Getenv("ALARM_IF_RMR") != "" {
+ go InitRMR(r)
}
- go InitRMR(r)
return r, nil
}
}
func (r *RICAlarm) sendAlarmUpdateReq(a AlarmMessage) error {
- if r.rmrCtx == nil || !r.rmrReady {
- return errors.New("RMR no ready yet!")
- }
-
- log.Printf("Alarm message: %+v\n", a)
- log.Println("Sending alarm: ", r.AlarmString(a))
payload, err := json.Marshal(a)
if err != nil {
+ log.Println("json.Marshal failed with error: ", err)
return err
}
+ log.Println("Sending alarm: ", fmt.Sprintf("%s", payload))
+
+ if r.rmrCtx == nil || !r.rmrReady {
+ url := fmt.Sprintf("%s/%s", r.adapterUrl, "ric/v1/alarms")
+ resp, err := http.Post(url, "application/json", bytes.NewReader(payload))
+ if err != nil || resp == nil {
+ return fmt.Errorf("Unable to send alarm: %v", err)
+ }
+ log.Printf("Alarm posted to %s [status=%d]", url, resp.StatusCode)
+ return nil
+ }
- log.Println("JSON payload: ", fmt.Sprintf("%s", payload))
datap := C.CBytes(payload)
defer C.free(datap)
meid := C.CString("ric")
func InitRMR(r *RICAlarm) error {
// Setup static RT for alarm system
- endpoint := "service-ricplt-alarmadapter-rmr.ricplt:4560"
+ endpoint := ALARM_ADAPTER_RMR_URL
if r.moId == "my-pod" {
endpoint = "localhost:4560"
} else if r.moId == "my-pod-lib" {
package alarm_test
import (
+ "encoding/json"
"github.com/stretchr/testify/assert"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "os"
"testing"
"time"
)
var alarmer *alarm.RICAlarm
+var adapterSim *httptest.Server
// Test cases
func TestAlarmInitSuccess(t *testing.T) {
+ os.Setenv("ALARM_ADAPTER_URL", "http://localhost:8080")
+ adapterSim = CreateAlarmAdapterSim(t, "POST", "/ric/v1/alarms", http.StatusOK, nil)
+
a, err := alarm.InitAlarm("my-pod-lib", "my-app")
assert.Nil(t, err, "init failed")
assert.Equal(t, false, a == nil)
a := alarmer.NewAlarm(1234, alarm.SeverityMajor, "Some App data", "eth 0 1")
assert.Equal(t, a.ApplicationId, "new-app")
}
+
+func TestTeardown(t *testing.T) {
+ adapterSim.Close()
+}
+
+func CreateAlarmAdapterSim(t *testing.T, method, url string, status int, respData interface{}) *httptest.Server {
+ l, err := net.Listen("tcp", "localhost:8080")
+ if err != nil {
+ t.Error("Failed to create listener: " + err.Error())
+ }
+ ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, r.Method, method)
+ assert.Equal(t, r.URL.String(), url)
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(status)
+ b, _ := json.Marshal(respData)
+ w.Write(b)
+ }))
+ ts.Listener.Close()
+ ts.Listener = l
+
+ ts.Start()
+
+ return ts
+}
// RICAlarm is an alarm instance
type RICAlarm struct {
- moId string
- appId string
- rmrCtx unsafe.Pointer
- rmrReady bool
- mutex sync.Mutex
+ moId string
+ appId string
+ adapterUrl string
+ rmrCtx unsafe.Pointer
+ rmrReady bool
+ mutex sync.Mutex
}
const (
}
var RICAlarmDefinitions = map[int]AlarmDefinition{
- RIC_RT_DISTRIBUTION_FAILED: AlarmDefinition{
+ RIC_RT_DISTRIBUTION_FAILED: {
AlarmId: RIC_RT_DISTRIBUTION_FAILED,
AlarmText: "RIC ROUTING TABLE DISTRIBUTION FAILED",
EventType: "Processing error",
OperationInstructions: "Not defined",
},
- TCP_CONNECTIVITY_LOST_TO_DBAAS: AlarmDefinition{
+ TCP_CONNECTIVITY_LOST_TO_DBAAS: {
AlarmId: TCP_CONNECTIVITY_LOST_TO_DBAAS,
AlarmText: "TCP CONNECTIVITY LOST TO DBAAS",
EventType: "Communication error",
OperationInstructions: "Not defined",
},
- E2_CONNECTIVITY_LOST_TO_GNODEB: AlarmDefinition{
+ E2_CONNECTIVITY_LOST_TO_GNODEB: {
AlarmId: E2_CONNECTIVITY_LOST_TO_GNODEB,
AlarmText: "E2 CONNECTIVITY LOST TO G-NODEB",
EventType: "Communication error",
OperationInstructions: "Not defined",
},
- E2_CONNECTIVITY_LOST_TO_ENODEB: AlarmDefinition{
+ E2_CONNECTIVITY_LOST_TO_ENODEB: {
AlarmId: E2_CONNECTIVITY_LOST_TO_ENODEB,
AlarmText: "E2 CONNECTIVITY LOST TO E-NODEB",
EventType: "Communication error",
OperationInstructions: "Not defined",
},
}
+
+const (
+ ALARM_ADAPTER_HTTP_URL string = "http://service-ricplt-alarmadapter-http.ricplt:8080"
+ ALARM_ADAPTER_RMR_URL string = "service-ricplt-alarmadapter-rmr.ricplt:4560"
+)
"maxSize": 1024,
"numWorkers": 1
},
- "promAlertManager": {
- "address": "elfkp-prometheus-alertmanager:9093",
- "baseUrl": "/api/v2",
- "schemes": "http",
- "alertInterval": 30000
- },
- "alarmDefinitions": [
- {
- "specificProblem": 1234,
- "Text": "Connection to DBAAS server lost"
- },
- {
- "specificProblem": 1235,
- "Text": "Distribution of internal routing table failed due to timeout"
+ "controls": {
+ "promAlertManager": {
+ "address": "elfkp-prometheus-alertmanager:9093",
+ "baseUrl": "/api/v2",
+ "schemes": "http",
+ "alertInterval": 30000
},
- {
- "specificProblem": 1236,
- "Text": "E2 connection to gNB lost"
- }
- ]
+ "maxActiveAlarms": 5000,
+ "maxAlarmHistory": 20000
+ }
}