[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
+}