[VOL-2310] Logging - Implement dynamic log levels in OpenOLT adapter

Working on Logging design changes.changes are added so how we can use voltha-lib-go to set dynamic logging configuration for component.

Change-Id: Iefd41bd23a84cee06735380837eea9054515ce80
diff --git a/VERSION b/VERSION
index b29ec1d..5aa7c52 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.3.9-dev
+2.3.9
diff --git a/main.go b/main.go
index 05479c9..296b42f 100644
--- a/main.go
+++ b/main.go
@@ -31,6 +31,7 @@
 
 	"github.com/opencord/voltha-lib-go/v3/pkg/adapters"
 	com "github.com/opencord/voltha-lib-go/v3/pkg/adapters/common"
+	conf "github.com/opencord/voltha-lib-go/v3/pkg/config"
 	"github.com/opencord/voltha-lib-go/v3/pkg/db/kvstore"
 	"github.com/opencord/voltha-lib-go/v3/pkg/kafka"
 	"github.com/opencord/voltha-lib-go/v3/pkg/log"
@@ -99,6 +100,10 @@
 		p.UpdateStatus("kv-store", probe.ServiceStatusRunning)
 	}
 
+	// Setup Log Config
+	cm := conf.NewConfigManager(a.kvClient, a.config.KVStoreType, a.config.KVStoreHost, a.config.KVStorePort, a.config.KVStoreTimeout)
+	go conf.ProcessLogConfigChange(cm, ctx)
+
 	// Setup Kafka Client
 	if a.kafkaClient, err = newKafkaClient("sarama", a.config.KafkaAdapterHost, a.config.KafkaAdapterPort); err != nil {
 		log.Fatal("Unsupported-common-client")
@@ -312,6 +317,7 @@
 		return err
 	}
 	a.kvClient = client
+
 	return nil
 }
 
diff --git a/vendor/github.com/opencord/voltha-lib-go/v3/pkg/config/configmanager.go b/vendor/github.com/opencord/voltha-lib-go/v3/pkg/config/configmanager.go
new file mode 100644
index 0000000..8f96b22
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/v3/pkg/config/configmanager.go
@@ -0,0 +1,215 @@
+/*
+ * 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 config
+
+import (
+	"context"
+	"fmt"
+	"github.com/opencord/voltha-lib-go/v3/pkg/db"
+	"github.com/opencord/voltha-lib-go/v3/pkg/db/kvstore"
+	"github.com/opencord/voltha-lib-go/v3/pkg/log"
+	"strings"
+)
+
+const (
+	defaultkvStoreConfigPath = "config"
+	kvStoreDataPathPrefix    = "/service/voltha"
+	kvStorePathSeparator     = "/"
+)
+
+// ConfigType represents the type for which config is created inside the kvstore
+// For example, loglevel
+type ConfigType int
+
+const (
+	ConfigTypeLogLevel ConfigType = iota
+	ConfigTypeKafka
+)
+
+func (c ConfigType) String() string {
+	return [...]string{"loglevel", "kafka"}[c]
+}
+
+// ChangeEvent represents the event recieved from watch
+// For example, Put Event
+type ChangeEvent int
+
+const (
+	Put ChangeEvent = iota
+	Delete
+)
+
+// ConfigChangeEvent represents config for the events recieved from watch
+// For example,ChangeType is Put ,ConfigAttribute default
+type ConfigChangeEvent struct {
+	ChangeType      ChangeEvent
+	ConfigAttribute string
+}
+
+// ConfigManager is a wrapper over backend to maintain Configuration of voltha components
+// in kvstore based persistent storage
+type ConfigManager struct {
+	backend             *db.Backend
+	KvStoreConfigPrefix string
+}
+
+// ComponentConfig represents a category of configuration for a specific VOLTHA component type
+// stored in a persistent storage pointed to by Config Manager
+// For example, one ComponentConfig instance will be created for loglevel config type for rw-core
+// component while another ComponentConfig instance will refer to connection config type for same
+// rw-core component. So, there can be multiple ComponentConfig instance created per component
+// pointing to different category of configuration.
+//
+// Configuration pointed to be by ComponentConfig is stored in kvstore as a list of key/value pairs
+// under the hierarchical tree with following base path
+// <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/
+//
+// For example, rw-core ComponentConfig for loglevel config entries will be stored under following path
+// /voltha/service/config/rw-core/loglevel/
+type ComponentConfig struct {
+	cManager         *ConfigManager
+	componentLabel   string
+	configType       ConfigType
+	changeEventChan  chan *ConfigChangeEvent
+	kvStoreEventChan chan *kvstore.Event
+}
+
+func NewConfigManager(kvClient kvstore.Client, kvStoreType, kvStoreHost string, kvStorePort, kvStoreTimeout int) *ConfigManager {
+
+	return &ConfigManager{
+		KvStoreConfigPrefix: defaultkvStoreConfigPath,
+		backend: &db.Backend{
+			Client:     kvClient,
+			StoreType:  kvStoreType,
+			Host:       kvStoreHost,
+			Port:       kvStorePort,
+			Timeout:    kvStoreTimeout,
+			PathPrefix: kvStoreDataPathPrefix,
+		},
+	}
+}
+
+// Initialize the component config
+func (cm *ConfigManager) InitComponentConfig(componentLabel string, configType ConfigType) *ComponentConfig {
+
+	return &ComponentConfig{
+		componentLabel:   componentLabel,
+		configType:       configType,
+		cManager:         cm,
+		changeEventChan:  nil,
+		kvStoreEventChan: nil,
+	}
+
+}
+
+func (c *ComponentConfig) makeConfigPath() string {
+
+	cType := c.configType.String()
+	return c.cManager.KvStoreConfigPrefix + kvStorePathSeparator +
+		c.componentLabel + kvStorePathSeparator + cType
+}
+
+// MonitorForConfigChange watch on the subkeys for the given key
+// Any changes to the subkeys for the given key will return an event channel
+// Then Event channel will be processed and  new event channel with required values will be created and return
+// For example, rw-core will be watching on <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/
+// will return an event channel for PUT,DELETE eventType.
+// Then values from event channel will be processed and  stored in kvStoreEventChan.
+func (c *ComponentConfig) MonitorForConfigChange(ctx context.Context) chan *ConfigChangeEvent {
+	key := c.makeConfigPath()
+
+	log.Debugw("monitoring-for-config-change", log.Fields{"key": key})
+
+	c.changeEventChan = make(chan *ConfigChangeEvent, 1)
+
+	c.kvStoreEventChan = c.cManager.backend.CreateWatch(ctx, key, true)
+
+	go c.processKVStoreWatchEvents()
+
+	return c.changeEventChan
+}
+
+// processKVStoreWatchEvents process event channel recieved from the backend for any ChangeType
+// It checks for the EventType is valid or not.For the valid EventTypes creates ConfigChangeEvent and send it on channel
+func (c *ComponentConfig) processKVStoreWatchEvents() {
+
+	ccKeyPrefix := c.makeConfigPath()
+	log.Debugw("processing-kvstore-event-change", log.Fields{"key-prefix": ccKeyPrefix})
+	ccPathPrefix := c.cManager.backend.PathPrefix + ccKeyPrefix + kvStorePathSeparator
+	for watchResp := range c.kvStoreEventChan {
+
+		if watchResp.EventType == kvstore.CONNECTIONDOWN || watchResp.EventType == kvstore.UNKNOWN {
+			log.Warnw("received-invalid-change-type-in-watch-channel-from-kvstore", log.Fields{"change-type": watchResp.EventType})
+			continue
+		}
+
+		// populating the configAttribute from the received Key
+		// For Example, Key received would be <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/default
+		// Storing default in configAttribute variable
+		ky := fmt.Sprintf("%s", watchResp.Key)
+
+		c.changeEventChan <- &ConfigChangeEvent{
+			ChangeType:      ChangeEvent(watchResp.EventType),
+			ConfigAttribute: strings.TrimPrefix(ky, ccPathPrefix),
+		}
+	}
+}
+
+func (c *ComponentConfig) RetrieveAll(ctx context.Context) (map[string]string, error) {
+	key := c.makeConfigPath()
+
+	log.Debugw("retreiving-list", log.Fields{"key": key})
+	data, err := c.cManager.backend.List(ctx, key)
+	if err != nil {
+		return nil, err
+	}
+
+	// Looping through the data recieved from the backend for the given key
+	// Trimming the required key and value from data and  storing as key/value pair
+	// For Example, recieved key would be <Backend Prefix Path>/<Config Prefix>/<Component Name>/<Config Type>/default and value \"DEBUG\"
+	// Then in default will be stored as key and DEBUG will be stored as value in map[string]string
+	res := make(map[string]string)
+	ccPathPrefix := c.cManager.backend.PathPrefix + kvStorePathSeparator + key + kvStorePathSeparator
+	for attr, val := range data {
+		res[strings.TrimPrefix(attr, ccPathPrefix)] = strings.Trim(fmt.Sprintf("%s", val.Value), "\"")
+	}
+
+	return res, nil
+}
+
+func (c *ComponentConfig) Save(configKey string, configValue string, ctx context.Context) error {
+	key := c.makeConfigPath() + "/" + configKey
+
+	log.Debugw("saving-key", log.Fields{"key": key, "value": configValue})
+
+	//save the data for update config
+	if err := c.cManager.backend.Put(ctx, key, configValue); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (c *ComponentConfig) Delete(configKey string, ctx context.Context) error {
+	//construct key using makeConfigPath
+	key := c.makeConfigPath() + "/" + configKey
+
+	log.Debugw("deleting-key", log.Fields{"key": key})
+	//delete the config
+	if err := c.cManager.backend.Delete(ctx, key); err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/vendor/github.com/opencord/voltha-lib-go/v3/pkg/config/logcontroller.go b/vendor/github.com/opencord/voltha-lib-go/v3/pkg/config/logcontroller.go
new file mode 100644
index 0000000..b45c2c8
--- /dev/null
+++ b/vendor/github.com/opencord/voltha-lib-go/v3/pkg/config/logcontroller.go
@@ -0,0 +1,289 @@
+/*
+ * 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 Config provides dynamic logging configuration for specific Voltha component type implemented using backend.The package can be used in following manner
+// Any Voltha component type can start dynamic logging by starting goroutine of ProcessLogConfigChange after starting kvClient for the component.
+
+package config
+
+import (
+	"context"
+	"crypto/md5"
+	"encoding/json"
+	"errors"
+	"github.com/opencord/voltha-lib-go/v3/pkg/log"
+	"os"
+	"strings"
+)
+
+// ComponentLogController represents a Configuration for Logging Config of specific Voltha component type
+// It stores ComponentConfig and GlobalConfig of loglevel config of specific Voltha component type
+// For example,ComponentLogController instance will be created for rw-core component
+type ComponentLogController struct {
+	ComponentName       string
+	componentNameConfig *ComponentConfig
+	GlobalConfig        *ComponentConfig
+	configManager       *ConfigManager
+	logHash             [16]byte
+}
+
+func NewComponentLogController(cm *ConfigManager) (*ComponentLogController, error) {
+
+	log.Debug("creating-new-component-log-controller")
+	componentName := os.Getenv("COMPONENT_NAME")
+	if componentName == "" {
+		return nil, errors.New("Unable to retrieve PoD Component Name from Runtime env")
+	}
+
+	return &ComponentLogController{
+		ComponentName:       componentName,
+		componentNameConfig: nil,
+		GlobalConfig:        nil,
+		configManager:       cm,
+	}, nil
+
+}
+
+// ProcessLogConfigChange initialize component config and global config
+func ProcessLogConfigChange(cm *ConfigManager, ctx context.Context) {
+	cc, err := NewComponentLogController(cm)
+	if err != nil {
+		log.Errorw("unable-to-construct-component-log-controller-instance-for-log-config-monitoring", log.Fields{"error": err})
+		return
+	}
+
+	log.Debugw("processing-log-config-change", log.Fields{"cc": cc})
+
+	cc.GlobalConfig = cm.InitComponentConfig("global", ConfigTypeLogLevel)
+	log.Debugw("global-log-config", log.Fields{"cc-global-config": cc.GlobalConfig})
+
+	cc.componentNameConfig = cm.InitComponentConfig(cc.ComponentName, ConfigTypeLogLevel)
+	log.Debugw("component-log-config", log.Fields{"cc-component-name-config": cc.componentNameConfig})
+
+	cc.processLogConfig(ctx)
+}
+
+// ProcessLogConfig wait on componentn config and global config channel for any changes
+// Event channel will be recieved from backend for valid change type
+// Then data for componentn log config and global log config will be retrieved from backend and stored in updatedLogConfig in precedence order
+// If any changes in updatedLogConfig will be applied on component
+func (c *ComponentLogController) processLogConfig(ctx context.Context) {
+
+	componentConfigEventChan := c.componentNameConfig.MonitorForConfigChange(ctx)
+
+	globalConfigEventChan := c.GlobalConfig.MonitorForConfigChange(ctx)
+
+	// process the events for componentName and  global config
+	var configEvent *ConfigChangeEvent
+	for {
+		select {
+		case configEvent = <-globalConfigEventChan:
+		case configEvent = <-componentConfigEventChan:
+
+		}
+		log.Debugw("processing-log-config-change", log.Fields{"config-event": configEvent})
+
+		updatedLogConfig, err := c.buildUpdatedLogConfig(ctx)
+		if err != nil {
+			log.Warnw("unable-to-fetch-updated-log-config", log.Fields{"error": err})
+			continue
+		}
+
+		log.Debugw("applying-updated-log-config", log.Fields{"updated-log-config": updatedLogConfig})
+
+		if err := c.loadAndApplyLogConfig(updatedLogConfig); err != nil {
+			log.Warnw("unable-to-load-and-apply-log-config", log.Fields{"error": err})
+		}
+	}
+
+}
+
+// get active loglevel from the zap logger
+func getActiveLogLevel() map[string]string {
+	loglevel := make(map[string]string)
+
+	// now do the default log level
+	if level, err := log.LogLevelToString(log.GetDefaultLogLevel()); err == nil {
+		loglevel["default"] = level
+	}
+
+	// do the per-package log levels
+	for _, packageName := range log.GetPackageNames() {
+		level, err := log.GetPackageLogLevel(packageName)
+		if err != nil {
+			log.Warnw("unable-to-fetch-current-active-loglevel-for-package-name", log.Fields{"package-name": packageName, "error": err})
+		}
+
+		packagename := strings.ReplaceAll(packageName, "/", "#")
+		if l, err := log.LogLevelToString(level); err == nil {
+			loglevel[packagename] = l
+		}
+
+	}
+	log.Debugw("getting-log-levels-from-zap-logger", log.Fields{"log-level": loglevel})
+
+	return loglevel
+}
+
+func (c *ComponentLogController) getGlobalLogConfig(ctx context.Context) (string, error) {
+
+	globalDefaultLogLevel := ""
+	globalLogConfig, err := c.GlobalConfig.RetrieveAll(ctx)
+	if err != nil {
+		return "", err
+	}
+
+	if globalLevel, ok := globalLogConfig["default"]; ok {
+		if _, err := log.StringToLogLevel(globalLevel); err != nil {
+			log.Warnw("unsupported-loglevel-config-defined-at-global-context-pacakge-name", log.Fields{"log-level": globalLevel})
+		} else {
+			globalDefaultLogLevel = globalLevel
+		}
+	}
+	log.Debugw("retrieved-global-log-config", log.Fields{"global-log-config": globalLogConfig})
+
+	return globalDefaultLogLevel, nil
+}
+
+func (c *ComponentLogController) getComponentLogConfig(globalDefaultLogLevel string, ctx context.Context) (map[string]string, error) {
+	var defaultPresent bool
+	componentLogConfig, err := c.componentNameConfig.RetrieveAll(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	for componentKey, componentLevel := range componentLogConfig {
+		if _, err := log.StringToLogLevel(componentLevel); err != nil || componentKey == "" {
+			log.Warnw("unsupported-loglevel-config-defined-at-component-context", log.Fields{"package-name": componentKey, "log-level": componentLevel})
+			delete(componentLogConfig, componentKey)
+		} else {
+			if componentKey == "default" {
+				defaultPresent = true
+			}
+		}
+	}
+	if !defaultPresent {
+		if globalDefaultLogLevel != "" {
+			componentLogConfig["default"] = globalDefaultLogLevel
+		}
+	}
+	log.Debugw("retrieved-component-log-config", log.Fields{"component-log-level": componentLogConfig})
+
+	return componentLogConfig, nil
+}
+
+// buildUpdatedLogConfig retrieve the global logConfig and component logConfig  from backend
+// component logConfig stores the log config with precedence order
+// For example, If the global logConfig is set and component logConfig is set only for specific package then
+// component logConfig is stored with global logConfig  and component logConfig of specific package
+// For example, If the global logConfig is set and component logConfig is set for specific package and as well as for default then
+// component logConfig is stored with  component logConfig data only
+func (c *ComponentLogController) buildUpdatedLogConfig(ctx context.Context) (map[string]string, error) {
+	globalLogLevel, err := c.getGlobalLogConfig(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	componentLogConfig, err := c.getComponentLogConfig(globalLogLevel, ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	log.Debugw("building-and-updating-log-config", log.Fields{"component-log-config": componentLogConfig})
+	return componentLogConfig, nil
+}
+
+// load and apply the current configuration for component name
+// create hash of loaded configuration using GenerateLogConfigHash
+// if there is previous hash stored, compare the hash to stored hash
+// if there is any change will call UpdateLogLevels
+func (c *ComponentLogController) loadAndApplyLogConfig(logConfig map[string]string) error {
+	currentLogHash, err := GenerateLogConfigHash(logConfig)
+	if err != nil {
+		return err
+	}
+
+	log.Debugw("loading-and-applying-log-config", log.Fields{"log-config": logConfig})
+	if c.logHash != currentLogHash {
+		UpdateLogLevels(logConfig)
+		c.logHash = currentLogHash
+	}
+	return nil
+}
+
+// getDefaultLogLevel to return active default log level
+func getDefaultLogLevel(logConfig map[string]string) string {
+
+	for key, level := range logConfig {
+		if key == "default" {
+			return level
+		}
+	}
+	return ""
+}
+
+// createCurrentLogLevel loop through the activeLogLevels recieved from zap logger and updatedLogLevels recieved from buildUpdatedLogConfig
+// The packageName is present or not will be checked in updatedLogLevels ,if the package name is not present then updatedLogLevels will be updated with
+// the packageName and loglevel with  default log level
+func createCurrentLogLevel(activeLogLevels, updatedLogLevels map[string]string) map[string]string {
+	level := getDefaultLogLevel(updatedLogLevels)
+	for activeKey, activeLevel := range activeLogLevels {
+		if _, exist := updatedLogLevels[activeKey]; !exist {
+			if level != "" {
+				activeLevel = level
+			}
+			updatedLogLevels[activeKey] = activeLevel
+		}
+	}
+	return updatedLogLevels
+}
+
+// updateLogLevels update the loglevels for the component
+// retrieve active confguration from logger
+// compare with entries one by one and apply
+func UpdateLogLevels(logLevel map[string]string) {
+
+	activeLogLevels := getActiveLogLevel()
+	currentLogLevel := createCurrentLogLevel(activeLogLevels, logLevel)
+	for key, level := range currentLogLevel {
+		if key == "default" {
+			if l, err := log.StringToLogLevel(level); err == nil {
+				log.SetDefaultLogLevel(l)
+			}
+		} else {
+			pname := strings.ReplaceAll(key, "#", "/")
+			if _, err := log.AddPackage(log.JSON, log.DebugLevel, nil, pname); err != nil {
+				log.Warnw("unable-to-add-log-package", log.Fields{"package-name": pname, "error": err})
+			}
+			if l, err := log.StringToLogLevel(level); err == nil {
+				log.SetPackageLogLevel(pname, l)
+			}
+		}
+	}
+	log.Debugw("updated-log-level", log.Fields{"current-log-level": currentLogLevel})
+}
+
+// generate md5 hash of key value pairs appended into a single string
+// in order by key name
+func GenerateLogConfigHash(createHashLog map[string]string) ([16]byte, error) {
+	createHashLogBytes := []byte{}
+	levelData, err := json.Marshal(createHashLog)
+	if err != nil {
+		return [16]byte{}, err
+	}
+	createHashLogBytes = append(createHashLogBytes, levelData...)
+	return md5.Sum(createHashLogBytes), nil
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 9522796..120d8b2 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -67,6 +67,7 @@
 github.com/opencord/voltha-lib-go/v3/pkg/adapters
 github.com/opencord/voltha-lib-go/v3/pkg/adapters/adapterif
 github.com/opencord/voltha-lib-go/v3/pkg/adapters/common
+github.com/opencord/voltha-lib-go/v3/pkg/config
 github.com/opencord/voltha-lib-go/v3/pkg/db
 github.com/opencord/voltha-lib-go/v3/pkg/db/kvstore
 github.com/opencord/voltha-lib-go/v3/pkg/flows