[VOL-3656]: Implemented StopManagingDevice DMI API

Change-Id: Idce337f229af51a07022878cb32cc49c899fa579
diff --git a/docs/source/DMI_Server_README.md b/docs/source/DMI_Server_README.md
index 0fb41ff..1f3e660 100755
--- a/docs/source/DMI_Server_README.md
+++ b/docs/source/DMI_Server_README.md
@@ -166,6 +166,12 @@
   }
 }
 ```
+### StopManagingDevice API
+``` sh
+grpcurl -plaintext -d '{"name":"SomeOlt"}' 172.17.0.2:50075 dmi.NativeHWManagementService.StopManagingDevice{
+  "status": "OK_STATUS"
+}
+```
 ### List NativeMetricsManagementService APIs
 ``` sh
 $ grpcurl -plaintext 172.17.0.2:50075 list dmi.NativeMetricsManagementService
@@ -270,4 +276,4 @@
     ]
   }
 }
-```
+```
\ No newline at end of file
diff --git a/internal/bbsim/dmiserver/dmi_api_server.go b/internal/bbsim/dmiserver/dmi_api_server.go
index 3fed692..f64f801 100755
--- a/internal/bbsim/dmiserver/dmi_api_server.go
+++ b/internal/bbsim/dmiserver/dmi_api_server.go
@@ -74,10 +74,6 @@
 
 	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_hw_mgmt.go b/internal/bbsim/dmiserver/dmi_hw_mgmt.go
index 30c27f0..cf6410d 100755
--- a/internal/bbsim/dmiserver/dmi_hw_mgmt.go
+++ b/internal/bbsim/dmiserver/dmi_hw_mgmt.go
@@ -33,6 +33,10 @@
 	"google.golang.org/grpc/status"
 )
 
+const (
+	metricChannelSize = 100
+)
+
 func getUUID(seed string) string {
 	return guuid.NewMD5(guuid.Nil, []byte(seed)).String()
 }
@@ -61,6 +65,10 @@
 	dms.ponTransceiverUuids = make([]string, olt.NumPon)
 	dms.ponTransceiverCageUuids = make([]string, olt.NumPon)
 
+	// Start device metrics generator
+	dms.metricChannel = make(chan interface{}, metricChannelSize)
+	StartMetricGenerator(dms)
+
 	var components []*dmi.Component
 
 	// Create and store the component for transceivers and transceiver cages
@@ -244,8 +252,36 @@
 }
 
 //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")
+func (dms *DmiAPIServer) StopManagingDevice(ctx context.Context, req *dmi.StopManagingDeviceRequest) (*dmi.StopManagingDeviceResponse, error) {
+	logger.Debugf("StopManagingDevice API invoked")
+	if req == nil {
+		return &dmi.StopManagingDeviceResponse{Status: dmi.Status_ERROR_STATUS, Reason: dmi.Reason_UNKNOWN_DEVICE}, status.Errorf(codes.FailedPrecondition, "request is empty")
+	}
+
+	if req.Name == "" {
+		return &dmi.StopManagingDeviceResponse{Status: dmi.Status_ERROR_STATUS, Reason: dmi.Reason_UNKNOWN_DEVICE},
+			status.Errorf(codes.InvalidArgument, "'Name' can not be empty in the request")
+	}
+
+	// Stop the components/go routines created
+	StopMetricGenerator()
+
+	if dms.mPublisherCancelFunc != nil {
+		dms.mPublisherCancelFunc()
+	}
+
+	dms.deviceName = ""
+	dms.kafkaEndpoint = ""
+	dms.ipAddress = ""
+	dms.deviceSerial = ""
+	dms.ponTransceiverUuids = nil
+	dms.ponTransceiverCageUuids = nil
+	dms.uuid = ""
+	dms.root = nil
+	dms.metricChannel = nil
+
+	logger.Infof("Stopped managing the device")
+	return &dmi.StopManagingDeviceResponse{Status: dmi.Status_OK_STATUS}, nil
 }
 
 //GetPhysicalInventory gets the HW inventory details of the Device
@@ -370,6 +406,11 @@
 		return status.Errorf(codes.Internal, "stream to send is nil, can not send response from gRPC server")
 	}
 
+	//if component list is empty, return error
+	if dms.root == nil {
+		logger.Errorf("Error occurred, device is not managed")
+		return status.Errorf(codes.Internal, "Error occurred, device is not managed, please start managing device")
+	}
 	// Search for the component and return it
 	c := findComponent(dms.root.Children, req.ComponentUuid.Uuid)
 
diff --git a/internal/bbsim/dmiserver/dmi_metrics_generator.go b/internal/bbsim/dmiserver/dmi_metrics_generator.go
index f7bbd03..09deace 100755
--- a/internal/bbsim/dmiserver/dmi_metrics_generator.go
+++ b/internal/bbsim/dmiserver/dmi_metrics_generator.go
@@ -17,6 +17,8 @@
 package dmiserver
 
 import (
+	"context"
+	log "github.com/sirupsen/logrus"
 	"math/rand"
 	"sync"
 	"time"
@@ -25,6 +27,9 @@
 	dmi "github.com/opencord/device-management-interface/go/dmi"
 )
 
+//MetricGenerationFunc to generate the metrics to the kafka bus
+type MetricGenerationFunc func(*dmi.Component, *DmiAPIServer) *dmi.Metric
+
 // MetricTriggerConfig is the configuration of a metric and the time at which it will be exported
 type MetricTriggerConfig struct {
 	cfg dmi.MetricConfig
@@ -36,13 +41,14 @@
 	apiSrv            *DmiAPIServer
 	configuredMetrics map[dmi.MetricNames]MetricTriggerConfig
 	access            sync.Mutex
+	mgCancelFunc      context.CancelFunc
 }
 
 var dmiMG DmiMetricsGenerator
 
 //StartMetricGenerator starts the metric generator
 func StartMetricGenerator(apiSrv *DmiAPIServer) {
-
+	log.Debugf("StartMetricGenerator invoked")
 	// Seed the rand for use later on
 	rand.Seed(time.Now().UnixNano())
 
@@ -105,8 +111,32 @@
 		t:   time.Unix(0, 0),
 	}
 
-	go func() {
-		for {
+	StartGeneratingMetrics()
+}
+
+// StartGeneratingMetrics starts the goroutine which submits metrics to the metrics channel
+func StartGeneratingMetrics() {
+	if dmiMG.apiSrv == nil {
+		// Metric Generator is not yet initialized/started.
+		// Means that the device is not managed on the DMI interface
+		return
+	}
+
+	// initialize a new context
+	var mgCtx context.Context
+	mgCtx, dmiMG.mgCancelFunc = context.WithCancel(context.Background())
+
+	go generateMetrics(mgCtx)
+}
+
+func generateMetrics(ctx context.Context) {
+loop:
+	for {
+		select {
+		case <-ctx.Done():
+			log.Infof("Stopping generation of metrics ")
+			break loop
+		default:
 			c := make(map[dmi.MetricNames]MetricTriggerConfig)
 
 			dmiMG.access.Lock()
@@ -184,7 +214,7 @@
 			}
 			time.Sleep(1 * time.Second)
 		}
-	}()
+	}
 }
 
 func sendOutMetric(metric interface{}, apiSrv *DmiAPIServer) {
@@ -344,3 +374,22 @@
 	}
 	return nil
 }
+
+// StopGeneratingMetrics stops the goroutine which submits metrics to the metrics channel
+func StopGeneratingMetrics() {
+	if dmiMG.mgCancelFunc != nil {
+		dmiMG.mgCancelFunc()
+	}
+}
+
+// StopMetricGenerator stops the generation of metrics and cleans up all local context
+func StopMetricGenerator() {
+	logger.Debugf("StopMetricGenerator invoked")
+
+	StopGeneratingMetrics()
+
+	dmiMG.access.Lock()
+	// reset it to an empty map
+	dmiMG.configuredMetrics = make(map[dmi.MetricNames]MetricTriggerConfig)
+	dmiMG.access.Unlock()
+}
diff --git a/internal/bbsim/dmiserver/dmi_metrics_mgmt.go b/internal/bbsim/dmiserver/dmi_metrics_mgmt.go
index 3f33941..cb88cc7 100755
--- a/internal/bbsim/dmiserver/dmi_metrics_mgmt.go
+++ b/internal/bbsim/dmiserver/dmi_metrics_mgmt.go
@@ -45,7 +45,9 @@
 
 	if req == nil || req.Operation == nil {
 		return &dmi.MetricsConfigurationResponse{
-			Status: dmi.Status_UNDEFINED_STATUS,
+			Status: dmi.Status_ERROR_STATUS,
+			//TODO reason must be INVALID_PARAMS, currently this is available in Device Management interface (DMI),
+			// change below reason with type INVALID_PARAMS once DMI is updated
 			Reason: dmi.Reason_UNDEFINED_REASON,
 		}, status.Errorf(codes.FailedPrecondition, "request is nil")
 	}
@@ -77,10 +79,20 @@
 	if req == nil || req.GetMetricId() < 0 {
 		return &dmi.GetMetricResponse{
 			Status: dmi.Status_ERROR_STATUS,
+			//TODO reason must be INVALID_PARAMS, currently this is available in Device Management interface (DMI),
+			// change below reason with type INVALID_PARAMS once DMI is updated
 			Reason: dmi.Reason_UNDEFINED_REASON,
 			Metric: &dmi.Metric{},
 		}, status.Errorf(codes.FailedPrecondition, "request is nil")
 	}
+
+	if dms.root == nil {
+		return &dmi.GetMetricResponse{
+			Status: dmi.Status_ERROR_STATUS,
+			Reason: dmi.Reason_INTERNAL_ERROR,
+			Metric: &dmi.Metric{},
+		}, status.Errorf(codes.FailedPrecondition, "Device is not managed, please start managing device to get the metric")
+	}
 	comp := findComponent(dms.root.Children, req.MetaData.ComponentUuid.Uuid)
 	metric := getMetric(comp, req.GetMetricId())
 	return &dmi.GetMetricResponse{