[VOL-3199] Added support for dynamic enable/disable of Trace Publishing for running components

Change-Id: Iddf7d04e4795a3c64abca216e9f106953c76601e
diff --git a/internal/pkg/commands/log.go b/internal/pkg/commands/log.go
index 52ab6cb..036abdc 100644
--- a/internal/pkg/commands/log.go
+++ b/internal/pkg/commands/log.go
@@ -39,6 +39,7 @@
 	defaultComponentName = "global"
 	defaultPackageName   = "default"
 	logPackagesListKey   = "log_package_list" // kvstore key containing list of allowed log packages
+	logTracingStatusKey  = "trace_publish"
 )
 
 // Custom Option representing <component-name>#<package-name> format (package is optional)
@@ -63,7 +64,7 @@
 	Error         string
 }
 
-// SetLogLevelOpts represents the supported CLI arguments for the loglevel set command
+// SetLogLevelOpts represents the supported CLI arguments for the log level set command
 type SetLogLevelOpts struct {
 	OutputOptions
 	Args struct {
@@ -72,7 +73,7 @@
 	} `positional-args:"yes" required:"yes"`
 }
 
-// ListLogLevelOpts represents the supported CLI arguments for the loglevel list command
+// ListLogLevelOpts represents the supported CLI arguments for the log level list command
 type ListLogLevelsOpts struct {
 	ListOutputOptions
 	Args struct {
@@ -80,7 +81,7 @@
 	} `positional-args:"yes" required:"yes"`
 }
 
-// ClearLogLevelOpts represents the supported CLI arguments for the loglevel clear command
+// ClearLogLevelOpts represents the supported CLI arguments for the log level clear command
 type ClearLogLevelsOpts struct {
 	OutputOptions
 	Args struct {
@@ -88,7 +89,7 @@
 	} `positional-args:"yes" required:"yes"`
 }
 
-// ListLogLevelOpts represents the supported CLI arguments for the loglevel list command
+// ListLogLevelOpts represents the supported CLI arguments for the log level list command
 type ListLogPackagesOpts struct {
 	ListOutputOptions
 	Args struct {
@@ -96,6 +97,30 @@
 	} `positional-args:"yes" required:"yes"`
 }
 
+//  EnableLogTracingOpts represents the supported CLI arguments for the log tracing enable command
+type EnableLogTracingOpts struct {
+	OutputOptions
+	Args struct {
+		Component []ComponentName
+	} `positional-args:"yes" required:"yes"`
+}
+
+//  DisableLogTracingOpts represents the supported CLI arguments for the log tracing disable command
+type DisableLogTracingOpts struct {
+	OutputOptions
+	Args struct {
+		Component []ComponentName
+	} `positional-args:"yes" required:"yes"`
+}
+
+//  ListLogTracingOpts represents the supported CLI arguments for the log tracing list command
+type ListLogTracingOpts struct {
+	ListOutputOptions
+	Args struct {
+		Component []ComponentName
+	} `positional-args:"yes" required:"yes"`
+}
+
 // LogPackageOpts represents the log package commands
 type LogPackageOpts struct {
 	ListLogPackages ListLogPackagesOpts `command:"list"`
@@ -108,18 +133,28 @@
 	ClearLogLevels ClearLogLevelsOpts `command:"clear"`
 }
 
+// LogTracingOpts represents the log tracing commands
+type LogTracingOpts struct {
+	EnableLogTracing  EnableLogTracingOpts  `command:"enable"`
+	DisableLogTracing DisableLogTracingOpts `command:"disable"`
+	ListLogTracing    ListLogTracingOpts    `command:"list"`
+}
+
 // LogOpts represents the log commands
 type LogOpts struct {
 	LogLevel   LogLevelOpts   `command:"level"`
 	LogPackage LogPackageOpts `command:"package"`
+	LogTracing LogTracingOpts `command:"tracing"`
 }
 
 var logOpts = LogOpts{}
 
 const (
-	DEFAULT_LOG_LEVELS_FORMAT   = "table{{ .ComponentName }}\t{{.PackageName}}\t{{.Level}}"
-	DEFAULT_LOG_PACKAGES_FORMAT = "table{{ .ComponentName }}\t{{.PackageName}}"
-	DEFAULT_LOG_RESULT_FORMAT   = "table{{ .ComponentName }}\t{{.PackageName}}\t{{.Status}}\t{{.Error}}"
+	DEFAULT_LOG_LEVELS_FORMAT         = "table{{ .ComponentName }}\t{{.PackageName}}\t{{.Level}}"
+	DEFAULT_LOG_PACKAGES_FORMAT       = "table{{ .ComponentName }}\t{{.PackageName}}"
+	DEFAULT_LOG_FEATURE_STATUS_FORMAT = "table{{ .ComponentName }}\t{{.Status}}"
+	DEFAULT_LOG_RESULT_FORMAT         = "table{{ .ComponentName }}\t{{.PackageName}}\t{{.Status}}\t{{.Error}}"
+	DEFAULT_LOG_FEATURE_RESULT_FORMAT = "table{{ .ComponentName }}\t{{.Status}}\t{{.Error}}"
 )
 
 func toStringArray(arg interface{}) []string {
@@ -143,7 +178,7 @@
 
 // RegisterLogCommands is used to register log and its sub-commands e.g. level, package etc
 func RegisterLogCommands(parent *flags.Parser) {
-	_, err := parent.AddCommand("log", "log config commands", "list, set, clear log levels and list packages of components", &logOpts)
+	_, err := parent.AddCommand("log", "log configuration commands", "update/view log levels, correlation, tracing status and list packages of components", &logOpts)
 	if err != nil {
 		Error.Fatalf("Unable to register log commands with voltctl command parser: %s", err.Error())
 	}
@@ -181,6 +216,18 @@
 	return componentList
 }
 
+func constructConfigManager(ctx context.Context) (*config.ConfigManager, func(), error) {
+	client, err := kvstore.NewEtcdClient(ctx, GlobalConfig.KvStore, GlobalConfig.KvStoreConfig.Timeout, log.FatalLevel)
+	if err != nil {
+		return nil, nil, fmt.Errorf("Unable to create kvstore client %s", err)
+	}
+
+	// Already error checked during option processing
+	host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
+	cm := config.NewConfigManager(ctx, client, supportedKvStoreType, net.JoinHostPort(host, strconv.Itoa(port)), GlobalConfig.KvStoreConfig.Timeout)
+	return cm, func() { client.Close(ctx) }, nil
+}
+
 // Method to get list of allowed Package Names for a given component. This list
 // is saved into etcd kvstore by each active component at startup as a json array
 func getPackageNames(componentName string) ([]string, error) {
@@ -190,19 +237,15 @@
 
 	log.SetAllLogLevel(log.FatalLevel)
 
-	client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, GlobalConfig.KvStoreConfig.Timeout, log.FatalLevel)
-	if err != nil {
-		return nil, fmt.Errorf("Unable to create kvstore client %s", err)
-	}
-	defer client.Close()
-
-	// Already error checked during option processing
-	host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
-	cm := config.NewConfigManager(client, supportedKvStoreType, net.JoinHostPort(host, strconv.Itoa(port)), GlobalConfig.KvStoreConfig.Timeout)
-
 	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
 	defer cancel()
 
+	cm, cleanupFunc, err := constructConfigManager(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("Error while constructing ConfigManager instance : %s", err)
+	}
+	defer cleanupFunc()
+
 	componentMetadata := cm.InitComponentConfig(componentName, config.ConfigTypeMetadata)
 
 	value, err := componentMetadata.Retrieve(ctx, logPackagesListKey)
@@ -315,18 +358,14 @@
 
 	log.SetAllLogLevel(log.FatalLevel)
 
-	client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, GlobalConfig.KvStoreConfig.Timeout, log.FatalLevel)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+	defer cancel()
+
+	cm, cleanupFunc, err := constructConfigManager(ctx)
 	if err != nil {
 		return list
 	}
-	defer client.Close()
-
-	// Already error checked during option processing
-	host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
-	cm := config.NewConfigManager(client, supportedKvStoreType, net.JoinHostPort(host, strconv.Itoa(port)), GlobalConfig.KvStoreConfig.Timeout)
-
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
-	defer cancel()
+	defer cleanupFunc()
 
 	var componentNames []string
 	componentNames, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
@@ -501,21 +540,17 @@
 		return fmt.Errorf(err.Error())
 	}
 
-	client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, GlobalConfig.KvStoreConfig.Timeout, log.FatalLevel)
-	if err != nil {
-		return fmt.Errorf("Unable to create kvstore client %s", err)
-	}
-	defer client.Close()
-
-	// Already error checked during option processing
-	host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
-	cm := config.NewConfigManager(client, supportedKvStoreType, net.JoinHostPort(host, strconv.Itoa(port)), GlobalConfig.KvStoreConfig.Timeout)
-
-	var output []LogLevelOutput
-
 	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
 	defer cancel()
 
+	cm, cleanupFunc, err := constructConfigManager(ctx)
+	if err != nil {
+		return fmt.Errorf("Error while constructing ConfigManager instance : %s", err)
+	}
+	defer cleanupFunc()
+
+	var output []LogLevelOutput
+
 	validComponents := getVolthaComponentNames()
 
 	for _, lConfig := range logLevelConfig {
@@ -608,22 +643,19 @@
 
 	log.SetAllLogLevel(log.FatalLevel)
 
-	client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, GlobalConfig.KvStoreConfig.Timeout, log.FatalLevel)
-	if err != nil {
-		return fmt.Errorf("Unable to create kvstore client %s", err)
-	}
-	defer client.Close()
-
-	// Already error checked during option processing
-	host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
-	cm := config.NewConfigManager(client, supportedKvStoreType, net.JoinHostPort(host, strconv.Itoa(port)), GlobalConfig.KvStoreConfig.Timeout)
-
 	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
 	defer cancel()
 
+	cm, cleanupFunc, err := constructConfigManager(ctx)
+	if err != nil {
+		return fmt.Errorf("Error while constructing ConfigManager instance : %s", err)
+	}
+	defer cleanupFunc()
+
 	if len(options.Args.Component) == 0 {
 		componentList, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
 		if err != nil {
+			host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
 			return fmt.Errorf("Unable to retrieve list of voltha components : %s \nIs ETCD available at %s:%d?", err, host, port)
 		}
 	} else {
@@ -730,21 +762,17 @@
 		return fmt.Errorf("%s", err)
 	}
 
-	client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, GlobalConfig.KvStoreConfig.Timeout, log.FatalLevel)
-	if err != nil {
-		return fmt.Errorf("Unable to create kvstore client %s", err)
-	}
-	defer client.Close()
-
-	// Already error checked during option processing
-	host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
-	cm := config.NewConfigManager(client, supportedKvStoreType, net.JoinHostPort(host, strconv.Itoa(port)), GlobalConfig.KvStoreConfig.Timeout)
-
-	var output []LogLevelOutput
-
 	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
 	defer cancel()
 
+	cm, cleanupFunc, err := constructConfigManager(ctx)
+	if err != nil {
+		return fmt.Errorf("Error while constructing ConfigManager instance : %s", err)
+	}
+	defer cleanupFunc()
+
+	var output []LogLevelOutput
+
 	for _, lConfig := range logLevelConfig {
 
 		if lConfig.ComponentName == defaultComponentName {
@@ -796,19 +824,15 @@
 
 	log.SetAllLogLevel(log.FatalLevel)
 
-	client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, GlobalConfig.KvStoreConfig.Timeout, log.FatalLevel)
-	if err != nil {
-		return fmt.Errorf("Unable to create kvstore client %s", err)
-	}
-	defer client.Close()
-
-	// Already error checked during option processing
-	host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
-	cm := config.NewConfigManager(client, supportedKvStoreType, net.JoinHostPort(host, strconv.Itoa(port)), GlobalConfig.KvStoreConfig.Timeout)
-
 	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
 	defer cancel()
 
+	cm, cleanupFunc, err := constructConfigManager(ctx)
+	if err != nil {
+		return fmt.Errorf("Error while constructing ConfigManager instance : %s", err)
+	}
+	defer cleanupFunc()
+
 	if len(options.Args.Component) == 0 {
 		componentList, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
 		if err != nil {
@@ -866,3 +890,264 @@
 	GenerateOutput(&result)
 	return nil
 }
+
+// This method enables log trace publishing for components.
+// For example, using below command, trace publishing can be enabled for specific component
+// voltctl log tracing enable <componentName>
+// Omitting the component name will enable trace publishing for all the components, as shown in below command.
+// voltctl log tracing enable
+func (options *EnableLogTracingOpts) Execute(args []string) error {
+
+	var (
+		componentNames []string
+		err            error
+	)
+
+	ProcessGlobalOptions()
+
+	log.SetAllLogLevel(log.FatalLevel)
+
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+	defer cancel()
+
+	cm, cleanupFunc, err := constructConfigManager(ctx)
+	if err != nil {
+		return fmt.Errorf("Error while constructing ConfigManager instance : %s", err)
+	}
+	defer cleanupFunc()
+
+	var output []LogLevelOutput
+
+	if len(options.Args.Component) == 0 {
+		// Apply to all components if no specific component has been indicated
+		componentNames, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogFeatures)
+		if err != nil {
+			return fmt.Errorf("Unable to retrieve list of voltha components : %s ", err)
+		}
+
+	} else {
+		for _, name := range options.Args.Component {
+			componentNames = append(componentNames, string(name))
+		}
+	}
+
+	validComponents := getVolthaComponentNames()
+
+	for _, component := range componentNames {
+
+		config := cm.InitComponentConfig(component, config.ConfigTypeLogFeatures)
+
+		err := config.Save(ctx, logTracingStatusKey, "ENABLED")
+		if err != nil {
+			output = append(output, LogLevelOutput{ComponentName: component, Status: "Failure", Error: err.Error()})
+		} else {
+			outmsg := ""
+			cvalid := false
+			for _, cname := range validComponents {
+				if component == cname {
+					cvalid = true
+					break
+				}
+			}
+
+			// For invalid component, add * against its name to indicate possible mis-configuration
+			if !cvalid {
+				component = "*" + component
+				outmsg = "Entered Component Name is not Currently active in Voltha"
+			}
+
+			output = append(output, LogLevelOutput{ComponentName: component, Status: "Success", Error: outmsg})
+		}
+	}
+
+	outputFormat := CharReplacer.Replace(options.Format)
+	if outputFormat == "" {
+		outputFormat = GetCommandOptionWithDefault("log-tracing-enable", "format", DEFAULT_LOG_FEATURE_RESULT_FORMAT)
+	}
+
+	result := CommandResult{
+		Format:    format.Format(outputFormat),
+		OutputAs:  toOutputType(options.OutputAs),
+		NameLimit: options.NameLimit,
+		Data:      output,
+	}
+
+	GenerateOutput(&result)
+	return nil
+}
+
+// This method disables log trace publishing for components.
+// For example, using below command, trace publishing can be disabled for specific component
+// voltctl log tracing disable <componentName>
+// Omitting the component name will disable trace publishing for all the components, as shown in below command.
+// voltctl log tracing disable
+func (options *DisableLogTracingOpts) Execute(args []string) error {
+
+	var (
+		componentNames []string
+		err            error
+	)
+
+	ProcessGlobalOptions()
+
+	log.SetAllLogLevel(log.FatalLevel)
+
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+	defer cancel()
+
+	cm, cleanupFunc, err := constructConfigManager(ctx)
+	if err != nil {
+		return fmt.Errorf("Error while constructing ConfigManager instance : %s", err)
+	}
+	defer cleanupFunc()
+
+	var output []LogLevelOutput
+
+	if len(options.Args.Component) == 0 {
+		// Apply to all components if no specific component has been indicated
+		componentNames, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogFeatures)
+		if err != nil {
+			return fmt.Errorf("Unable to retrieve list of voltha components : %s ", err)
+		}
+
+	} else {
+		for _, name := range options.Args.Component {
+			componentNames = append(componentNames, string(name))
+		}
+	}
+
+	validComponents := getVolthaComponentNames()
+
+	for _, component := range componentNames {
+
+		config := cm.InitComponentConfig(component, config.ConfigTypeLogFeatures)
+
+		err := config.Save(ctx, logTracingStatusKey, "DISABLED")
+
+		if err != nil {
+			output = append(output, LogLevelOutput{ComponentName: component, Status: "Failure", Error: err.Error()})
+		} else {
+			outmsg := ""
+			cvalid := false
+			for _, cname := range validComponents {
+				if component == cname {
+					cvalid = true
+					break
+				}
+			}
+
+			// For invalid component, add * against its name to indicate possible mis-configuration
+			if !cvalid {
+				component = "*" + component
+				outmsg = "Entered Component Name is not Currently active in Voltha"
+			}
+
+			output = append(output, LogLevelOutput{ComponentName: component, Status: "Success", Error: outmsg})
+		}
+	}
+
+	outputFormat := CharReplacer.Replace(options.Format)
+	if outputFormat == "" {
+		outputFormat = GetCommandOptionWithDefault("log-tracing-disable", "format", DEFAULT_LOG_FEATURE_RESULT_FORMAT)
+	}
+
+	result := CommandResult{
+		Format:    format.Format(outputFormat),
+		OutputAs:  toOutputType(options.OutputAs),
+		NameLimit: options.NameLimit,
+		Data:      output,
+	}
+
+	GenerateOutput(&result)
+	return nil
+}
+
+// This method lists current status of log trace publishing for components.
+// For example, using below command, trace publishing can be queried for specific component
+// voltctl log tracing list <componentName>
+// Omitting the component name will list trace publishing for all the components, as shown in below command.
+// voltctl log tracing list
+func (options *ListLogTracingOpts) Execute(args []string) error {
+
+	var (
+		data           []model.LogFeature = []model.LogFeature{}
+		componentNames []string
+		err            error
+	)
+
+	ProcessGlobalOptions()
+
+	log.SetAllLogLevel(log.FatalLevel)
+
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+	defer cancel()
+
+	cm, cleanupFunc, err := constructConfigManager(ctx)
+	if err != nil {
+		return fmt.Errorf("Error while constructing ConfigManager instance : %s", err)
+	}
+	defer cleanupFunc()
+
+	if len(options.Args.Component) == 0 {
+		// Apply to all components if no specific component has been indicated
+		componentNames, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogFeatures)
+		if err != nil {
+			return fmt.Errorf("Unable to retrieve list of voltha components : %s ", err)
+		}
+
+	} else {
+		for _, name := range options.Args.Component {
+			componentNames = append(componentNames, string(name))
+		}
+	}
+
+	validComponents := getVolthaComponentNames()
+
+	for _, component := range componentNames {
+
+		config := cm.InitComponentConfig(component, config.ConfigTypeLogFeatures)
+
+		value, err := config.Retrieve(ctx, logTracingStatusKey)
+		if err != nil || value == "" {
+			// Ignore any error in retrieval; move to next component
+			continue
+		}
+
+		cvalid := false
+		for _, cname := range validComponents {
+			if component == cname {
+				cvalid = true
+				break
+			}
+		}
+
+		// For invalid component, add * against its name to indicate possible mis-configuration
+		if !cvalid {
+			component = "*" + component
+		}
+
+		logTracingStatus := model.LogFeature{}
+		logTracingStatus.PopulateFrom(component, value)
+		data = append(data, logTracingStatus)
+	}
+
+	outputFormat := CharReplacer.Replace(options.Format)
+	if outputFormat == "" {
+		outputFormat = GetCommandOptionWithDefault("log-tracing-list", "format", DEFAULT_LOG_FEATURE_STATUS_FORMAT)
+	}
+	orderBy := options.OrderBy
+	if orderBy == "" {
+		orderBy = GetCommandOptionWithDefault("log-tracing-list", "order", "ComponentName,Status")
+	}
+
+	result := CommandResult{
+		Format:    format.Format(outputFormat),
+		Filter:    options.Filter,
+		OrderBy:   orderBy,
+		OutputAs:  toOutputType(options.OutputAs),
+		NameLimit: options.NameLimit,
+		Data:      data,
+	}
+	GenerateOutput(&result)
+	return nil
+}