[VOL:3643] support of some OLT device metrics over the Device Management Interface
1. Following metrices are supported :
METRIC_FAN_SPEED
METRIC_CPU_USAGE_PERCENTAGE
METRIC_RAM_USAGE_PERCENTAGE
METRIC_DISK_USAGE_PERCENTAGE
METRIC_INNER_SURROUNDING_TEMP
2. Following DMI APIs are implemented:
ListMetrics
UpdateMetricsConfiguration
GetMetric
SetMsgBusEndpoint
GetMsgBusEndpoint
3. Updated docs/source/DMI_Server_README.md
Change-Id: I11f988ff972b8a8682012c7aeea88ba61afb82ba
diff --git a/internal/bbsim/dmiserver/dmi_api_server.go b/internal/bbsim/dmiserver/dmi_api_server.go
old mode 100644
new mode 100755
index e90f852..3fed692
--- a/internal/bbsim/dmiserver/dmi_api_server.go
+++ b/internal/bbsim/dmiserver/dmi_api_server.go
@@ -17,6 +17,7 @@
package dmiserver
import (
+ "context"
"net"
"github.com/opencord/bbsim/internal/common"
@@ -39,7 +40,10 @@
uuid string
ponTransceiverUuids []string
ponTransceiverCageUuids []string
- components []*dmi.Component
+ root *dmi.Component
+ metricChannel chan interface{}
+ kafkaEndpoint string
+ mPublisherCancelFunc context.CancelFunc
}
var dmiServ DmiAPIServer
@@ -70,6 +74,10 @@
go func() { _ = grpcServer.Serve(lis) }()
logger.Debugf("DMI grpc Server listening on %v", address)
+ //buffer upto 100 metrics
+ dms.metricChannel = make(chan interface{}, 100)
+
+ StartMetricGenerator(dms)
return grpcServer, nil
}
diff --git a/internal/bbsim/dmiserver/dmi_events_mgmt.go b/internal/bbsim/dmiserver/dmi_events_mgmt.go
old mode 100644
new mode 100755
index 6db9e84..3fc3416
--- a/internal/bbsim/dmiserver/dmi_events_mgmt.go
+++ b/internal/bbsim/dmiserver/dmi_events_mgmt.go
@@ -28,7 +28,7 @@
//empty events
events := []*dmi.EventCfg{{}}
return &dmi.ListEventsResponse{
- Status: dmi.Status_OK,
+ Status: dmi.Status_OK_STATUS,
Reason: dmi.Reason_UNDEFINED_REASON,
Events: &dmi.EventsCfg{
Items: events,
@@ -40,6 +40,6 @@
func (dms *DmiAPIServer) UpdateEventsConfiguration(ctx context.Context, req *dmi.EventsConfigurationRequest) (*dmi.EventsConfigurationResponse, error) {
logger.Debugf("UpdateEventsConfiguration called with request %+v", req)
return &dmi.EventsConfigurationResponse{
- Status: dmi.Status_OK,
+ Status: dmi.Status_OK_STATUS,
}, nil
}
diff --git a/internal/bbsim/dmiserver/dmi_hw_mgmt.go b/internal/bbsim/dmiserver/dmi_hw_mgmt.go
old mode 100644
new mode 100755
index ba5a0c4..30c27f0
--- a/internal/bbsim/dmiserver/dmi_hw_mgmt.go
+++ b/internal/bbsim/dmiserver/dmi_hw_mgmt.go
@@ -20,6 +20,8 @@
"context"
"fmt"
+ "github.com/Shopify/sarama"
+
"github.com/golang/protobuf/ptypes/empty"
"github.com/golang/protobuf/ptypes/timestamp"
"github.com/opencord/bbsim/internal/bbsim/devices"
@@ -94,11 +96,44 @@
components = append(components, &cage)
}
- dms.components = components
+ // create the fans
+ numFans := 2
+ fans := make([]*dmi.Component, numFans)
+
+ for i := 0; i < numFans; i++ {
+ fans[i] = createFanComponent(i + 1)
+ }
+ components = append(components, fans...)
+
+ // Create 1 disk, 1 Processor and 1 ram
+ components = append(components, createDiskComponent(0))
+ components = append(components, createProcessorComponent(0))
+ components = append(components, createMemoryComponent(0))
+ components = append(components, createInnerSurroundingTempComponentSensor(0))
+
+ // create the root component
+ dms.root = &dmi.Component{
+ Name: dms.deviceName,
+ Class: 0,
+ Description: "",
+ Parent: "",
+ ParentRelPos: 0,
+ Children: components,
+ SerialNum: dms.deviceSerial,
+ MfgName: common.Config.Olt.Vendor,
+ IsFru: false,
+ Uri: &dmi.Uri{
+ Uri: dms.ipAddress,
+ },
+ Uuid: &dmi.Uuid{
+ Uuid: dms.uuid,
+ },
+ State: &dmi.ComponentState{},
+ }
logger.Debugf("Generated UUID for the uri %s is %s", dms.ipAddress, dms.uuid)
response := &dmi.StartManagingDeviceResponse{
- Status: dmi.Status_OK,
+ Status: dmi.Status_OK_STATUS,
DeviceUuid: &dmi.Uuid{
Uuid: dms.uuid,
},
@@ -113,6 +148,101 @@
return nil
}
+func createFanComponent(fanIdx int) *dmi.Component {
+ fanName := fmt.Sprintf("Thermal/Fans/System Fan/%d", fanIdx)
+ fanSerial := fmt.Sprintf("bbsim-fan-serial-%d", fanIdx)
+ return &dmi.Component{
+ Name: fanName,
+ Class: dmi.ComponentType_COMPONENT_TYPE_FAN,
+ Description: "bbsim-fan",
+ Parent: "",
+ ParentRelPos: 0,
+ SerialNum: fanSerial,
+ MfgName: "bbsim-fan",
+ IsFru: false,
+ Uuid: &dmi.Uuid{
+ Uuid: getUUID(fanName),
+ },
+ State: &dmi.ComponentState{},
+ }
+}
+
+func createProcessorComponent(cpuIdx int) *dmi.Component {
+ cpuName := fmt.Sprintf("Systems/1/Processors/%d", cpuIdx)
+ cpuSerial := fmt.Sprintf("bbsim-cpu-serial-%d", cpuIdx)
+ return &dmi.Component{
+ Name: cpuName,
+ Class: dmi.ComponentType_COMPONENT_TYPE_CPU,
+ Description: "bbsim-cpu",
+ Parent: "",
+ ParentRelPos: 0,
+ SerialNum: cpuSerial,
+ MfgName: "bbsim-cpu",
+ IsFru: false,
+ Uuid: &dmi.Uuid{
+ Uuid: getUUID(cpuName),
+ },
+ State: &dmi.ComponentState{},
+ }
+}
+
+func createMemoryComponent(memIdx int) *dmi.Component {
+ memName := fmt.Sprintf("Systems/1/Memory/%d", memIdx)
+ memSerial := fmt.Sprintf("bbsim-ram-serial-%d", memIdx)
+ return &dmi.Component{
+ Name: memName,
+ Class: dmi.ComponentType_COMPONENT_TYPE_MEMORY,
+ Description: "bbsim-ram",
+ Parent: "",
+ ParentRelPos: 0,
+ SerialNum: memSerial,
+ MfgName: "bbsim-ram",
+ IsFru: false,
+ Uuid: &dmi.Uuid{
+ Uuid: getUUID(memName),
+ },
+ State: &dmi.ComponentState{},
+ }
+}
+
+func createDiskComponent(diskIdx int) *dmi.Component {
+ diskName := fmt.Sprintf("Systems/1/Disk/%d", diskIdx)
+ diskSerial := fmt.Sprintf("bbsim-disk-serial-%d", diskIdx)
+ return &dmi.Component{
+ Name: diskName,
+ Class: dmi.ComponentType_COMPONENT_TYPE_STORAGE,
+ Description: "bbsim-disk",
+ Parent: "",
+ ParentRelPos: 0,
+ SerialNum: diskSerial,
+ MfgName: "bbsim-disk",
+ IsFru: false,
+ Uuid: &dmi.Uuid{
+ Uuid: getUUID(diskName),
+ },
+ State: &dmi.ComponentState{},
+ }
+}
+
+func createInnerSurroundingTempComponentSensor(sensorIdx int) *dmi.Component {
+ sensorName := fmt.Sprintf("Systems/1/Sensor/%d", sensorIdx)
+ sensorSerial := fmt.Sprintf("bbsim-sensor-istemp-serial-%d", sensorIdx)
+ return &dmi.Component{
+ Name: sensorName,
+ Class: dmi.ComponentType_COMPONENT_TYPE_SENSOR,
+ Description: "bbsim-istemp",
+ Parent: "",
+ ParentRelPos: 0,
+ SerialNum: sensorSerial,
+ MfgName: "bbsim-istemp",
+ IsFru: false,
+ Uuid: &dmi.Uuid{
+ Uuid: getUUID(sensorName),
+ },
+ State: &dmi.ComponentState{},
+ }
+}
+
//StopManagingDevice stops management of a device and cleans up any context and caches for that device
func (dms *DmiAPIServer) StopManagingDevice(context.Context, *dmi.StopManagingDeviceRequest) (*dmi.StopManagingDeviceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "rpc StopManagingDevice not implemented")
@@ -138,7 +268,7 @@
logger.Errorf("Requested uuid =%s, uuid of existing device = %s", req.DeviceUuid.Uuid, dms.uuid)
// Wrong uuid, return error
errResponse := &dmi.PhysicalInventoryResponse{
- Status: dmi.Status_ERROR,
+ Status: dmi.Status_ERROR_STATUS,
Reason: dmi.Reason_UNKNOWN_DEVICE,
Inventory: &dmi.Hardware{},
}
@@ -147,30 +277,13 @@
}
response := &dmi.PhysicalInventoryResponse{
- Status: dmi.Status_OK,
+ Status: dmi.Status_OK_STATUS,
Inventory: &dmi.Hardware{
LastChange: ×tamp.Timestamp{
Seconds: 0,
Nanos: 0,
},
- Root: &dmi.Component{
- Name: dms.deviceName,
- Class: 0,
- Description: "",
- Parent: "",
- ParentRelPos: 0,
- Children: dms.components,
- SerialNum: dms.deviceSerial,
- MfgName: common.Config.Olt.Vendor,
- IsFru: false,
- Uri: &dmi.Uri{
- Uri: dms.ipAddress,
- },
- Uuid: &dmi.Uuid{
- Uuid: dms.uuid,
- },
- State: &dmi.ComponentState{},
- },
+ Root: dms.root,
},
}
return sendResponseBackOnStream(stream, response)
@@ -179,7 +292,6 @@
//Contains tells whether arr contains element.
func Contains(arr []string, element string) bool {
for _, item := range arr {
- logger.Debugf("Checking in Contains slice elem = %v str = %s", item, element)
if element == item {
return true
}
@@ -188,29 +300,47 @@
}
func findComponent(l []*dmi.Component, compUUID string) *dmi.Component {
+ var foundComp *dmi.Component
+
for _, comp := range l {
logger.Debugf("findComponent slice comp = %v compUUID = %s", comp, compUUID)
if comp.Uuid.Uuid == compUUID {
return comp
}
- for _, child := range comp.GetChildren() {
- logger.Debugf("findComponent Child slice comp = %v compUUID = %s", comp, compUUID)
- if child.Uuid.Uuid == compUUID {
- return child
- }
+ foundComp = findComponent(comp.GetChildren(), compUUID)
+ if foundComp != nil {
+ return foundComp
}
}
return nil
}
+func findComponentsOfType(l []*dmi.Component, compType dmi.ComponentType) []*dmi.Component {
+ var comps []*dmi.Component
+ findComponents(l, compType, &comps)
+ return comps
+}
+
+func findComponents(l []*dmi.Component, compType dmi.ComponentType, collector *[]*dmi.Component) {
+
+ for _, comp := range l {
+ if comp.Class == compType {
+ *collector = append(*collector, comp)
+ //logger.Debugf("Added collector = %v", *collector)
+ }
+
+ findComponents(comp.GetChildren(), compType, collector)
+ }
+}
+
func sendGetHWComponentResponse(c *dmi.Component, stream dmi.NativeHWManagementService_GetHWComponentInfoServer) error {
- apiStatus := dmi.Status_OK
+ apiStatus := dmi.Status_OK_STATUS
reason := dmi.Reason_UNDEFINED_REASON
if c == nil {
- apiStatus = dmi.Status_ERROR
+ apiStatus = dmi.Status_ERROR_STATUS
reason = dmi.Reason_UNKNOWN_DEVICE
}
@@ -240,18 +370,9 @@
return status.Errorf(codes.Internal, "stream to send is nil, can not send response from gRPC server")
}
- componentFound := Contains(dms.ponTransceiverUuids, req.ComponentUuid.Uuid)
- if !componentFound {
- componentFound = Contains(dms.ponTransceiverCageUuids, req.ComponentUuid.Uuid)
- }
-
- if req.DeviceUuid.Uuid != dms.uuid || !componentFound {
- // Wrong uuid, return error
- return sendGetHWComponentResponse(nil, stream)
- }
-
// Search for the component and return it
- c := findComponent(dms.components, req.ComponentUuid.Uuid)
+ c := findComponent(dms.root.Children, req.ComponentUuid.Uuid)
+
return sendGetHWComponentResponse(c, stream)
}
@@ -266,18 +387,55 @@
}
//GetLoggingEndpoint gets the configured location to which the logs are being shipped
-func (dms *DmiAPIServer) GetLoggingEndpoint(context.Context, *dmi.Uuid) (*dmi.GetLoggingEndpointResponse, error) {
+func (dms *DmiAPIServer) GetLoggingEndpoint(context.Context, *dmi.HardwareID) (*dmi.GetLoggingEndpointResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "rpc GetLoggingEndpoint not implemented")
}
//SetMsgBusEndpoint sets the location of the Message Bus to which events and metrics are shipped
-func (dms *DmiAPIServer) SetMsgBusEndpoint(context.Context, *dmi.SetMsgBusEndpointRequest) (*dmi.SetRemoteEndpointResponse, error) {
- return nil, status.Errorf(codes.Unimplemented, "rpc SetMsgBusEndpoint not implemented")
+func (dms *DmiAPIServer) SetMsgBusEndpoint(ctx context.Context, request *dmi.SetMsgBusEndpointRequest) (*dmi.SetRemoteEndpointResponse, error) {
+ logger.Debugf("SetMsgBusEndpoint() invoked with request: %+v and context: %v", request, ctx)
+ if request == nil || request.MsgbusEndpoint == "" {
+ return &dmi.SetRemoteEndpointResponse{Status: dmi.Status_ERROR_STATUS, Reason: dmi.Reason_KAFKA_ENDPOINT_ERROR},
+ status.Errorf(codes.FailedPrecondition, "request is nil")
+ }
+ olt := devices.GetOLT()
+ dms.kafkaEndpoint = request.MsgbusEndpoint
+
+ // close the old publisher
+ if dms.mPublisherCancelFunc != nil {
+ dms.mPublisherCancelFunc()
+ }
+
+ // initialize a new publisher
+ var nCtx context.Context
+ nCtx, dms.mPublisherCancelFunc = context.WithCancel(context.Background())
+ // initialize a publisher
+ if err := InitializeDMKafkaPublishers(sarama.NewAsyncProducer, olt.ID, dms.kafkaEndpoint); err == nil {
+ // start a go routine which will read from channel and publish on kafka
+ go DMKafkaPublisher(nCtx, dms.metricChannel, "dm.metrics")
+ } else {
+ logger.Errorf("Failed to start kafka publisher: %v", err)
+ return &dmi.SetRemoteEndpointResponse{Status: dmi.Status_ERROR_STATUS, Reason: dmi.Reason_KAFKA_ENDPOINT_ERROR}, err
+ }
+
+ return &dmi.SetRemoteEndpointResponse{Status: dmi.Status_OK_STATUS, Reason: dmi.Reason_UNDEFINED_REASON}, nil
}
//GetMsgBusEndpoint gets the configured location to which the events and metrics are being shipped
func (dms *DmiAPIServer) GetMsgBusEndpoint(context.Context, *empty.Empty) (*dmi.GetMsgBusEndpointResponse, error) {
- return nil, status.Errorf(codes.Unimplemented, "rpc GetMsgBusEndpoint not implemented")
+ logger.Debugf("GetMsgBusEndpoint() invoked")
+ if dms.kafkaEndpoint != "" {
+ return &dmi.GetMsgBusEndpointResponse{
+ Status: dmi.Status_OK_STATUS,
+ Reason: dmi.Reason_UNDEFINED_REASON,
+ MsgbusEndpoint: dms.kafkaEndpoint,
+ }, nil
+ }
+ return &dmi.GetMsgBusEndpointResponse{
+ Status: dmi.Status_ERROR_STATUS,
+ Reason: dmi.Reason_KAFKA_ENDPOINT_ERROR,
+ MsgbusEndpoint: "",
+ }, nil
}
//GetManagedDevices returns an object containing a list of devices managed by this entity
@@ -297,3 +455,35 @@
return &retResponse, nil
}
+
+//GetLogLevel Gets the configured log level for a certain entity on a certain device.
+func (dms *DmiAPIServer) GetLogLevel(context.Context, *dmi.GetLogLevelRequest) (*dmi.GetLogLevelResponse, error) {
+ return &dmi.GetLogLevelResponse{
+ Status: dmi.Status_OK_STATUS,
+ DeviceUuid: &dmi.Uuid{
+ Uuid: dms.uuid,
+ },
+ LogLevels: []*dmi.EntitiesLogLevel{},
+ }, nil
+}
+
+// SetLogLevel Sets the log level of the device, for each given entity to a certain level.
+func (dms *DmiAPIServer) SetLogLevel(context.Context, *dmi.SetLogLevelRequest) (*dmi.SetLogLevelResponse, error) {
+ return &dmi.SetLogLevelResponse{
+ Status: dmi.Status_OK_STATUS,
+ DeviceUuid: &dmi.Uuid{
+ Uuid: dms.uuid,
+ },
+ }, nil
+}
+
+// GetLoggableEntities Gets the entities of a device on which log can be configured.
+func (dms *DmiAPIServer) GetLoggableEntities(context.Context, *dmi.GetLoggableEntitiesRequest) (*dmi.GetLogLevelResponse, error) {
+ return &dmi.GetLogLevelResponse{
+ Status: dmi.Status_OK_STATUS,
+ DeviceUuid: &dmi.Uuid{
+ Uuid: dms.uuid,
+ },
+ LogLevels: []*dmi.EntitiesLogLevel{},
+ }, nil
+}
diff --git a/internal/bbsim/dmiserver/dmi_kafka_producer.go b/internal/bbsim/dmiserver/dmi_kafka_producer.go
new file mode 100755
index 0000000..9ee0172
--- /dev/null
+++ b/internal/bbsim/dmiserver/dmi_kafka_producer.go
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * 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 dmiserver
+
+import (
+ "context"
+ "encoding/json"
+ "strconv"
+ "time"
+
+ "github.com/Shopify/sarama"
+ log "github.com/sirupsen/logrus"
+)
+
+var metricsProducer sarama.AsyncProducer
+
+// InitializeDMKafkaPublishers initializes metrics kafka publisher
+func InitializeDMKafkaPublishers(NewAsyncProducer func([]string, *sarama.Config) (sarama.AsyncProducer, error), oltID int, msgBusEndPoint string) error {
+ var err error
+ sarama.Logger = log.New()
+ // producer config
+ config := sarama.NewConfig()
+ config.Producer.Retry.Max = 5
+ config.Metadata.Retry.Max = 10
+ config.Metadata.Retry.Backoff = 10 * time.Second
+ config.ClientID = "BBSim-OLT-Metrics-" + strconv.Itoa(oltID)
+
+ metricsProducer, err = NewAsyncProducer([]string{msgBusEndPoint}, config)
+ return err
+}
+
+// DMKafkaPublisher receives messages on ch and publish them to kafka on topic
+func DMKafkaPublisher(ctx context.Context, ch chan interface{}, topic string) {
+ defer log.Debugf("DMKafkaPublisher stopped")
+loop:
+ for {
+ select {
+ case metric := <-ch:
+ log.Tracef("Writing to kafka topic(%s): %v", topic, metric)
+ jsonMet, err := json.Marshal(metric)
+ if err != nil {
+ log.Errorf("Failed to get json metric %v", err)
+ continue
+ }
+ metricsProducer.Input() <- &sarama.ProducerMessage{
+ Topic: topic,
+ Value: sarama.ByteEncoder(jsonMet),
+ }
+ case <-ctx.Done():
+ log.Infof("Stopping DM Kafka Publisher for topic %s", topic)
+ break loop
+ }
+ }
+}
diff --git a/internal/bbsim/dmiserver/dmi_metrics_generator.go b/internal/bbsim/dmiserver/dmi_metrics_generator.go
new file mode 100755
index 0000000..f7bbd03
--- /dev/null
+++ b/internal/bbsim/dmiserver/dmi_metrics_generator.go
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * 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 dmiserver
+
+import (
+ "math/rand"
+ "sync"
+ "time"
+
+ "github.com/golang/protobuf/ptypes"
+ dmi "github.com/opencord/device-management-interface/go/dmi"
+)
+
+// MetricTriggerConfig is the configuration of a metric and the time at which it will be exported
+type MetricTriggerConfig struct {
+ cfg dmi.MetricConfig
+ t time.Time
+}
+
+//DmiMetricsGenerator has the attributes for generating metrics
+type DmiMetricsGenerator struct {
+ apiSrv *DmiAPIServer
+ configuredMetrics map[dmi.MetricNames]MetricTriggerConfig
+ access sync.Mutex
+}
+
+var dmiMG DmiMetricsGenerator
+
+//StartMetricGenerator starts the metric generator
+func StartMetricGenerator(apiSrv *DmiAPIServer) {
+
+ // Seed the rand for use later on
+ rand.Seed(time.Now().UnixNano())
+
+ dmiMG = DmiMetricsGenerator{
+ apiSrv: apiSrv,
+ }
+ dmiMG.configuredMetrics = make(map[dmi.MetricNames]MetricTriggerConfig)
+
+ // Add CPU usage as a default Metric reported every 60 secs
+ cpuMetricConfig := dmi.MetricConfig{
+ MetricId: dmi.MetricNames_METRIC_CPU_USAGE_PERCENTAGE,
+ IsConfigured: true,
+ PollInterval: 60,
+ }
+ dmiMG.configuredMetrics[dmi.MetricNames_METRIC_CPU_USAGE_PERCENTAGE] = MetricTriggerConfig{
+ cfg: cpuMetricConfig,
+ t: time.Unix(0, 0),
+ }
+
+ // Add FAN speed metric as a default Metric reported every 120 secs
+ fanSpeedMetricConfig := dmi.MetricConfig{
+ MetricId: dmi.MetricNames_METRIC_FAN_SPEED,
+ IsConfigured: true,
+ PollInterval: 120,
+ }
+ dmiMG.configuredMetrics[dmi.MetricNames_METRIC_FAN_SPEED] = MetricTriggerConfig{
+ cfg: fanSpeedMetricConfig,
+ t: time.Unix(0, 0),
+ }
+
+ // Add RAM usage percentage metric reported every 60 seconds
+ ramUsageMetricConfig := dmi.MetricConfig{
+ MetricId: dmi.MetricNames_METRIC_RAM_USAGE_PERCENTAGE,
+ IsConfigured: false,
+ PollInterval: 60,
+ }
+ dmiMG.configuredMetrics[dmi.MetricNames_METRIC_RAM_USAGE_PERCENTAGE] = MetricTriggerConfig{
+ cfg: ramUsageMetricConfig,
+ t: time.Unix(0, 0),
+ }
+
+ // Add DISK usage percentage metric reported every 60 seconds
+ diskUsageMetricConfig := dmi.MetricConfig{
+ MetricId: dmi.MetricNames_METRIC_DISK_USAGE_PERCENTAGE,
+ IsConfigured: false,
+ PollInterval: 60,
+ }
+ dmiMG.configuredMetrics[dmi.MetricNames_METRIC_DISK_USAGE_PERCENTAGE] = MetricTriggerConfig{
+ cfg: diskUsageMetricConfig,
+ t: time.Unix(0, 0),
+ }
+ // Add Inner Surrounding TEMP usage percentage metric reported every 120 seconds
+ innerTempUsageMetricConfig := dmi.MetricConfig{
+ MetricId: dmi.MetricNames_METRIC_INNER_SURROUNDING_TEMP,
+ IsConfigured: true,
+ PollInterval: 120,
+ }
+ dmiMG.configuredMetrics[dmi.MetricNames_METRIC_INNER_SURROUNDING_TEMP] = MetricTriggerConfig{
+ cfg: innerTempUsageMetricConfig,
+ t: time.Unix(0, 0),
+ }
+
+ go func() {
+ for {
+ c := make(map[dmi.MetricNames]MetricTriggerConfig)
+
+ dmiMG.access.Lock()
+ for k, v := range dmiMG.configuredMetrics {
+ c[k] = v
+ }
+ dmiMG.access.Unlock()
+
+ now := time.Now()
+ // For all the supported metrics
+ for k, v := range c {
+ if dmiMG.apiSrv.root == nil || dmiMG.apiSrv.root.Children == nil {
+ // inventory might not yet be created or somehow disappeared
+ break
+ }
+
+ if k == dmi.MetricNames_METRIC_CPU_USAGE_PERCENTAGE && v.cfg.IsConfigured {
+ if now.Before(v.t) {
+ continue
+ }
+ updateConfiguredMetrics(now, dmi.MetricNames_METRIC_CPU_USAGE_PERCENTAGE, &v)
+
+ // Get the CPUs
+ for _, cpu := range findComponentsOfType(dmiMG.apiSrv.root.Children, dmi.ComponentType_COMPONENT_TYPE_CPU) {
+ m := generateCPUUsageMetric(cpu, dmiMG.apiSrv)
+ logger.Debugf("Got metric %v", m)
+ sendOutMetric(m, dmiMG.apiSrv)
+ }
+ } else if k == dmi.MetricNames_METRIC_FAN_SPEED && v.cfg.IsConfigured {
+ if now.Before(v.t) {
+ continue
+ }
+ updateConfiguredMetrics(now, dmi.MetricNames_METRIC_FAN_SPEED, &v)
+
+ // Get the FANs
+ for _, fan := range findComponentsOfType(dmiMG.apiSrv.root.Children, dmi.ComponentType_COMPONENT_TYPE_FAN) {
+ m := generateFanSpeedMetric(fan, dmiMG.apiSrv)
+ logger.Debugf("Got metric %v", m)
+ sendOutMetric(m, dmiMG.apiSrv)
+ }
+ } else if k == dmi.MetricNames_METRIC_RAM_USAGE_PERCENTAGE && v.cfg.IsConfigured {
+ if now.Before(v.t) {
+ continue
+ }
+ updateConfiguredMetrics(now, dmi.MetricNames_METRIC_RAM_USAGE_PERCENTAGE, &v)
+ // Get the RAM
+ for _, ram := range findComponentsOfType(dmiMG.apiSrv.root.Children, dmi.ComponentType_COMPONENT_TYPE_MEMORY) {
+ m := generateRAMUsageMetric(ram, dmiMG.apiSrv)
+ logger.Debugf("Got metric for ram usage percentage %v", m)
+ sendOutMetric(m, dmiMG.apiSrv)
+ }
+ } else if k == dmi.MetricNames_METRIC_DISK_USAGE_PERCENTAGE && v.cfg.IsConfigured {
+ if now.Before(v.t) {
+ continue
+ }
+ updateConfiguredMetrics(now, dmi.MetricNames_METRIC_DISK_USAGE_PERCENTAGE, &v)
+ // Get the DISK
+ for _, disk := range findComponentsOfType(dmiMG.apiSrv.root.Children, dmi.ComponentType_COMPONENT_TYPE_STORAGE) {
+ m := generateDiskUsageMetric(disk, dmiMG.apiSrv)
+ logger.Debugf("Got metric for disk usage percentage %v", m)
+ sendOutMetric(m, dmiMG.apiSrv)
+ }
+ } else if k == dmi.MetricNames_METRIC_INNER_SURROUNDING_TEMP && v.cfg.IsConfigured {
+ if now.Before(v.t) {
+ continue
+ }
+ updateConfiguredMetrics(now, dmi.MetricNames_METRIC_INNER_SURROUNDING_TEMP, &v)
+ // Get the INNER SURROUNDING TEMPERATURE
+ for _, isTemp := range findComponentsOfType(dmiMG.apiSrv.root.Children, dmi.ComponentType_COMPONENT_TYPE_SENSOR) {
+ m := generateInnerSurroundingTempMetric(isTemp, dmiMG.apiSrv)
+ logger.Debugf("Got metric for inner surrounding temperature %v", m)
+ sendOutMetric(m, dmiMG.apiSrv)
+ }
+ }
+ }
+ time.Sleep(1 * time.Second)
+ }
+ }()
+}
+
+func sendOutMetric(metric interface{}, apiSrv *DmiAPIServer) {
+ select {
+ case apiSrv.metricChannel <- metric:
+ default:
+ logger.Debugf("Channel not ready dropping Metric")
+ }
+}
+
+func updateConfiguredMetrics(curr time.Time, typ dmi.MetricNames, old *MetricTriggerConfig) {
+ dmiMG.access.Lock()
+ dmiMG.configuredMetrics[typ] = MetricTriggerConfig{
+ cfg: old.cfg,
+ t: curr.Add(time.Second * time.Duration(old.cfg.PollInterval)),
+ }
+ dmiMG.access.Unlock()
+}
+
+func updateMetricIDAndMetaData(id dmi.MetricNames, c *dmi.Component, apiSrv *DmiAPIServer, m *dmi.Metric) *dmi.Metric {
+ m.MetricId = id
+ m.MetricMetadata = &dmi.MetricMetaData{
+ DeviceUuid: &dmi.Uuid{
+ Uuid: apiSrv.uuid,
+ },
+ ComponentUuid: c.Uuid,
+ ComponentName: c.Name,
+ }
+ return m
+}
+
+func generateCPUUsageMetric(cpu *dmi.Component, apiSrv *DmiAPIServer) *dmi.Metric {
+ var met dmi.Metric
+ met = *updateMetricIDAndMetaData(dmi.MetricNames_METRIC_CPU_USAGE_PERCENTAGE, cpu, apiSrv, &met)
+ met.Value = &dmi.ComponentSensorData{
+ Value: generateRand(1, 20),
+ Type: dmi.SensorValueType_SENSOR_VALUE_TYPE_OTHER,
+ Scale: dmi.SensorValueScale_SENSOR_VALUE_SCALE_UNITS,
+ Timestamp: ptypes.TimestampNow(),
+ }
+ return &met
+}
+
+func generateFanSpeedMetric(fan *dmi.Component, apiSrv *DmiAPIServer) *dmi.Metric {
+ var met dmi.Metric
+ met = *updateMetricIDAndMetaData(dmi.MetricNames_METRIC_FAN_SPEED, fan, apiSrv, &met)
+ met.Value = &dmi.ComponentSensorData{
+ Value: generateRand(3000, 4000),
+ Type: dmi.SensorValueType_SENSOR_VALUE_TYPE_RPM,
+ Scale: dmi.SensorValueScale_SENSOR_VALUE_SCALE_UNITS,
+ Timestamp: ptypes.TimestampNow(),
+ }
+ return &met
+}
+
+// return a random number RAND which is: lValue < RAND < hValue
+func generateRand(lValue, hValue int32) int32 {
+ if lValue >= hValue {
+ return 0
+ }
+
+ diff := hValue - lValue
+
+ randVal := rand.Int31n(diff)
+
+ return lValue + randVal
+}
+
+//UpdateMetricConfig Adds/Updates the passed metric configuration
+func UpdateMetricConfig(newCfg *dmi.MetricConfig) {
+ dmiMG.access.Lock()
+ dmiMG.configuredMetrics[newCfg.GetMetricId()] = MetricTriggerConfig{
+ cfg: *newCfg,
+ t: time.Unix(0, 0),
+ }
+ dmiMG.access.Unlock()
+ logger.Infof("Metric updated %v", newCfg)
+}
+
+func generateRAMUsageMetric(ram *dmi.Component, apiSrv *DmiAPIServer) *dmi.Metric {
+ var met dmi.Metric
+ met = *updateMetricIDAndMetaData(dmi.MetricNames_METRIC_RAM_USAGE_PERCENTAGE, ram, apiSrv, &met)
+ met.Value = &dmi.ComponentSensorData{
+ Value: generateRand(1, 8),
+ Type: dmi.SensorValueType_SENSOR_VALUE_TYPE_OTHER,
+ Scale: dmi.SensorValueScale_SENSOR_VALUE_SCALE_GIGA,
+ Timestamp: ptypes.TimestampNow(),
+ }
+ return &met
+}
+
+func generateDiskUsageMetric(disk *dmi.Component, apiSrv *DmiAPIServer) *dmi.Metric {
+ var met dmi.Metric
+ met = *updateMetricIDAndMetaData(dmi.MetricNames_METRIC_DISK_USAGE_PERCENTAGE, disk, apiSrv, &met)
+ met.Value = &dmi.ComponentSensorData{
+ Value: generateRand(50, 500),
+ Type: dmi.SensorValueType_SENSOR_VALUE_TYPE_OTHER,
+ Scale: dmi.SensorValueScale_SENSOR_VALUE_SCALE_GIGA,
+ Timestamp: ptypes.TimestampNow(),
+ }
+ return &met
+}
+
+func generateInnerSurroundingTempMetric(istemp *dmi.Component, apiSrv *DmiAPIServer) *dmi.Metric {
+ var met dmi.Metric
+ met = *updateMetricIDAndMetaData(dmi.MetricNames_METRIC_INNER_SURROUNDING_TEMP, istemp, apiSrv, &met)
+ met.Value = &dmi.ComponentSensorData{
+ Value: generateRand(30, 40),
+ Type: dmi.SensorValueType_SENSOR_VALUE_TYPE_CELSIUS,
+ Scale: dmi.SensorValueScale_SENSOR_VALUE_SCALE_UNITS,
+ Timestamp: ptypes.TimestampNow(),
+ }
+ return &met
+}
+
+// get the metrics list
+func getMetricsList() []*dmi.MetricConfig {
+ components := make(map[dmi.MetricNames]MetricTriggerConfig)
+ dmiMG.access.Lock()
+
+ for key, value := range dmiMG.configuredMetrics {
+ components[key] = value
+ }
+
+ dmiMG.access.Unlock()
+
+ var toRet []*dmi.MetricConfig
+ for _, v := range components {
+ metricConfig := v.cfg
+ toRet = append(toRet, &metricConfig)
+ }
+ logger.Debugf("Metrics list %+v", toRet)
+ return toRet
+}
+
+func getMetric(comp *dmi.Component, metricID dmi.MetricNames) *dmi.Metric {
+ switch metricID {
+ case dmi.MetricNames_METRIC_FAN_SPEED:
+ metric := generateFanSpeedMetric(comp, dmiMG.apiSrv)
+ return metric
+
+ case dmi.MetricNames_METRIC_CPU_USAGE_PERCENTAGE:
+ metric := generateCPUUsageMetric(comp, dmiMG.apiSrv)
+ return metric
+
+ case dmi.MetricNames_METRIC_RAM_USAGE_PERCENTAGE:
+ metric := generateRAMUsageMetric(comp, dmiMG.apiSrv)
+ return metric
+
+ case dmi.MetricNames_METRIC_DISK_USAGE_PERCENTAGE:
+ metric := generateDiskUsageMetric(comp, dmiMG.apiSrv)
+ return metric
+
+ case dmi.MetricNames_METRIC_INNER_SURROUNDING_TEMP:
+ metric := generateInnerSurroundingTempMetric(comp, dmiMG.apiSrv)
+ return metric
+ }
+ return nil
+}
diff --git a/internal/bbsim/dmiserver/dmi_metrics_mgmt.go b/internal/bbsim/dmiserver/dmi_metrics_mgmt.go
old mode 100644
new mode 100755
index 51b5dcc..3f33941
--- a/internal/bbsim/dmiserver/dmi_metrics_mgmt.go
+++ b/internal/bbsim/dmiserver/dmi_metrics_mgmt.go
@@ -19,16 +19,19 @@
import (
"context"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+
dmi "github.com/opencord/device-management-interface/go/dmi"
)
//ListMetrics lists the supported metrics for the passed device.
func (dms *DmiAPIServer) ListMetrics(ctx context.Context, req *dmi.HardwareID) (*dmi.ListMetricsResponse, error) {
logger.Debugf("ListMetrics invoked with request %+v", req)
- //return empty list of metrics for now
- metrics := []*dmi.MetricConfig{{}}
+ metrics := getMetricsList()
+
return &dmi.ListMetricsResponse{
- Status: dmi.Status_OK,
+ Status: dmi.Status_OK_STATUS,
Reason: 0,
Metrics: &dmi.MetricsConfig{
Metrics: metrics,
@@ -39,18 +42,50 @@
//UpdateMetricsConfiguration updates the configuration of the list of metrics in the request
func (dms *DmiAPIServer) UpdateMetricsConfiguration(ctx context.Context, req *dmi.MetricsConfigurationRequest) (*dmi.MetricsConfigurationResponse, error) {
logger.Debugf("UpdateMetricConfiguration invoked with request %+v", req)
+
+ if req == nil || req.Operation == nil {
+ return &dmi.MetricsConfigurationResponse{
+ Status: dmi.Status_UNDEFINED_STATUS,
+ Reason: dmi.Reason_UNDEFINED_REASON,
+ }, status.Errorf(codes.FailedPrecondition, "request is nil")
+ }
+
+ switch x := req.Operation.(type) {
+ case *dmi.MetricsConfigurationRequest_Changes:
+ for _, chMetric := range x.Changes.Metrics {
+ UpdateMetricConfig(chMetric)
+ }
+ case *dmi.MetricsConfigurationRequest_ResetToDefault:
+ logger.Debugf("To be implemented later")
+ case nil:
+ // The field is not set.
+ logger.Debugf("Update request operation type is nil")
+ return &dmi.MetricsConfigurationResponse{
+ Status: dmi.Status_UNDEFINED_STATUS,
+ }, nil
+ }
+
return &dmi.MetricsConfigurationResponse{
- Status: dmi.Status_OK,
+ Status: dmi.Status_OK_STATUS,
}, nil
}
//GetMetric gets the instantenous value of a metric
func (dms *DmiAPIServer) GetMetric(ctx context.Context, req *dmi.GetMetricRequest) (*dmi.GetMetricResponse, error) {
logger.Debugf("GetMetric invoked with request %+v", req)
- return &dmi.GetMetricResponse{
- Status: dmi.Status_OK,
- Reason: dmi.Reason_UNDEFINED_REASON,
- Metric: &dmi.Metric{},
- }, nil
+ if req == nil || req.GetMetricId() < 0 {
+ return &dmi.GetMetricResponse{
+ Status: dmi.Status_ERROR_STATUS,
+ Reason: dmi.Reason_UNDEFINED_REASON,
+ Metric: &dmi.Metric{},
+ }, status.Errorf(codes.FailedPrecondition, "request is nil")
+ }
+ comp := findComponent(dms.root.Children, req.MetaData.ComponentUuid.Uuid)
+ metric := getMetric(comp, req.GetMetricId())
+ return &dmi.GetMetricResponse{
+ Status: dmi.Status_OK_STATUS,
+ Reason: dmi.Reason_UNDEFINED_REASON,
+ Metric: metric,
+ }, nil
}
diff --git a/internal/bbsim/dmiserver/dmi_sw_mgmt.go b/internal/bbsim/dmiserver/dmi_sw_mgmt.go
old mode 100644
new mode 100755
index 0d24d9d..a1186d3
--- a/internal/bbsim/dmiserver/dmi_sw_mgmt.go
+++ b/internal/bbsim/dmiserver/dmi_sw_mgmt.go
@@ -27,7 +27,7 @@
// TODO: Make this more interesting by taking values from BBSim if available
logger.Debugf("GetSoftwareVersion invoked with for device %+v", req)
return &dmi.GetSoftwareVersionInformationResponse{
- Status: dmi.Status_OK,
+ Status: dmi.Status_OK_STATUS,
Reason: dmi.Reason_UNDEFINED_REASON,
Info: &dmi.SoftwareVersionInformation{
ActiveVersions: []*dmi.ImageVersion{{
@@ -46,7 +46,7 @@
func (dms *DmiAPIServer) DownloadImage(req *dmi.DownloadImageRequest, stream dmi.NativeSoftwareManagementService_DownloadImageServer) error {
logger.Debugf("DownloadImage invoked with request %+v", req)
err := stream.Send(&dmi.ImageStatus{
- Status: dmi.Status_OK,
+ Status: dmi.Status_OK_STATUS,
State: dmi.ImageStatus_COPYING_IMAGE,
})
if err != nil {
@@ -61,7 +61,7 @@
func (dms *DmiAPIServer) ActivateImage(req *dmi.HardwareID, stream dmi.NativeSoftwareManagementService_ActivateImageServer) error {
logger.Debugf("ActivateImage invoked with request %+v", req)
err := stream.Send(&dmi.ImageStatus{
- Status: dmi.Status_OK,
+ Status: dmi.Status_OK_STATUS,
State: dmi.ImageStatus_ACTIVATION_COMPLETE,
})
@@ -77,7 +77,7 @@
func (dms *DmiAPIServer) RevertToStandbyImage(req *dmi.HardwareID, stream dmi.NativeSoftwareManagementService_RevertToStandbyImageServer) error {
logger.Debugf("RevertToStandbyImage invoked with request %+v", req)
err := stream.Send(&dmi.ImageStatus{
- Status: dmi.Status_OK,
+ Status: dmi.Status_OK_STATUS,
State: dmi.ImageStatus_ACTIVATION_COMPLETE,
})
@@ -87,3 +87,9 @@
}
return nil
}
+
+// UpdateStartupConfiguration API can be used to let the devices pickup their properitary configuration which they need at startup.
+func (dms *DmiAPIServer) UpdateStartupConfiguration(*dmi.ConfigRequest, dmi.NativeSoftwareManagementService_UpdateStartupConfigurationServer) error {
+ logger.Debugf("UpdateStartupConfiguration invoked")
+ return nil
+}