[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: &timestamp.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
+}