[VOL-2899] Added loglevel listpackage command to voltctl for displaying
list of configured log packages.
[VOL-2919] Added support for auto-complete of component and package name for
all log level and log package commands
Change-Id: I4bfac812c4dead6e06cf38b21415c3f74321f0b3
diff --git a/cmd/voltctl/voltctl.go b/cmd/voltctl/voltctl.go
index 6a2524a..e3da44f 100644
--- a/cmd/voltctl/voltctl.go
+++ b/cmd/voltctl/voltctl.go
@@ -66,7 +66,7 @@
commands.RegisterCompletionCommands(parser)
commands.RegisterConfigCommands(parser)
commands.RegisterComponentCommands(parser)
- commands.RegisterLogLevelCommands(parser)
+ commands.RegisterLogCommands(parser)
commands.RegisterEventCommands(parser)
commands.RegisterMessageCommands(parser)
diff --git a/internal/pkg/commands/log.go b/internal/pkg/commands/log.go
new file mode 100644
index 0000000..fdd3739
--- /dev/null
+++ b/internal/pkg/commands/log.go
@@ -0,0 +1,773 @@
+/*
+ * Copyright 2019-present Ciena Corporation
+ *
+ * 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 commands
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "strings"
+
+ flags "github.com/jessevdk/go-flags"
+ "github.com/opencord/voltctl/pkg/format"
+ "github.com/opencord/voltctl/pkg/model"
+ "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/log"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/tools/clientcmd"
+)
+
+const (
+ defaultComponentName = "global"
+ defaultPackageName = "default"
+ logPackagesListKey = "log_package_list" // kvstore key containing list of allowed log packages
+)
+
+// Custom Option representing <component-name>#<package-name> format (package is optional)
+// This is used by 'log level set' commands
+type ComponentAndPackageName string
+
+// Custom Option representing currently configured log configuration in <component-name>#<package-name> format (package is optional)
+// This is used by 'log level clear' commands
+type ConfiguredComponentAndPackageName string
+
+// Custom Option representing component-name. This is used by 'log level list' and 'log package list' commands
+type ComponentName string
+
+// Custom Option representing Log Level (one of debug, info, warn, error, fatal)
+type LevelName string
+
+// LogLevelOutput represents the output structure for the loglevel
+type LogLevelOutput struct {
+ ComponentName string
+ Status string
+ Error string
+}
+
+// SetLogLevelOpts represents the supported CLI arguments for the loglevel set command
+type SetLogLevelOpts struct {
+ OutputOptions
+ Args struct {
+ Level LevelName
+ Component []ComponentAndPackageName
+ } `positional-args:"yes" required:"yes"`
+}
+
+// ListLogLevelOpts represents the supported CLI arguments for the loglevel list command
+type ListLogLevelsOpts struct {
+ ListOutputOptions
+ Args struct {
+ Component []ComponentName
+ } `positional-args:"yes" required:"yes"`
+}
+
+// ClearLogLevelOpts represents the supported CLI arguments for the loglevel clear command
+type ClearLogLevelsOpts struct {
+ OutputOptions
+ Args struct {
+ Component []ConfiguredComponentAndPackageName
+ } `positional-args:"yes" required:"yes"`
+}
+
+// ListLogLevelOpts represents the supported CLI arguments for the loglevel list command
+type ListLogPackagesOpts struct {
+ ListOutputOptions
+ Args struct {
+ Component []ComponentName
+ } `positional-args:"yes" required:"yes"`
+}
+
+// LogPackageOpts represents the log package commands
+type LogPackageOpts struct {
+ ListLogPackages ListLogPackagesOpts `command:"list"`
+}
+
+// LogLevelOpts represents the log level commands
+type LogLevelOpts struct {
+ SetLogLevel SetLogLevelOpts `command:"set"`
+ ListLogLevels ListLogLevelsOpts `command:"list"`
+ ClearLogLevels ClearLogLevelsOpts `command:"clear"`
+}
+
+// LogOpts represents the log commands
+type LogOpts struct {
+ LogLevel LogLevelOpts `command:"level"`
+ LogPackage LogPackageOpts `command:"package"`
+}
+
+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{{.Status}}\t{{.Error}}"
+)
+
+func toStringArray(arg interface{}) []string {
+ var list []string
+ if cnl, ok := arg.([]ComponentName); ok {
+ for _, cn := range cnl {
+ list = append(list, string(cn))
+ }
+ } else if cpnl, ok := arg.([]ComponentAndPackageName); ok {
+ for _, cpn := range cpnl {
+ list = append(list, string(cpn))
+ }
+ } else if ccpnl, ok := arg.([]ConfiguredComponentAndPackageName); ok {
+ for _, ccpn := range ccpnl {
+ list = append(list, string(ccpn))
+ }
+ }
+
+ return list
+}
+
+// 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)
+ if err != nil {
+ Error.Fatalf("Unable to register log commands with voltctl command parser: %s", err.Error())
+ }
+}
+
+// Common method to get list of VOLTHA components using k8s API. Used for validation and auto-complete
+// Just return a blank list in case of any error
+func getVolthaComponentNames() []string {
+ var componentList []string
+
+ // use the current context in kubeconfig
+ config, err := clientcmd.BuildConfigFromFlags("", GlobalOptions.K8sConfig)
+ if err != nil {
+ // Ignore any error
+ return componentList
+ }
+
+ // create the clientset
+ clientset, err := kubernetes.NewForConfig(config)
+ if err != nil {
+ return componentList
+ }
+
+ pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{
+ LabelSelector: "app.kubernetes.io/part-of=voltha",
+ })
+ if err != nil {
+ return componentList
+ }
+
+ for _, pod := range pods.Items {
+ componentList = append(componentList, pod.ObjectMeta.Labels["app.kubernetes.io/name"])
+ }
+
+ return componentList
+}
+
+func getPackageNames(componentName string) ([]string, error) {
+ list := []string{defaultPackageName}
+
+ ProcessGlobalOptions()
+
+ log.SetAllLogLevel(log.FatalLevel)
+
+ client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), 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, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
+
+ ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+ defer cancel()
+
+ componentMetadata := cm.InitComponentConfig(componentName, config.ConfigTypeMetadata)
+
+ value, err := componentMetadata.Retrieve(ctx, logPackagesListKey)
+ if err != nil || value == "" {
+ return list, nil
+ }
+
+ var packageList []string
+ if err = json.Unmarshal([]byte(value), &packageList); err != nil {
+ return list, nil
+ }
+
+ list = append(list, packageList...)
+
+ return list, nil
+}
+
+func (ln *LevelName) Complete(match string) []flags.Completion {
+ levels := []string{"debug", "info", "warn", "error", "fatal"}
+
+ var list []flags.Completion
+ for _, name := range levels {
+ if strings.HasPrefix(name, strings.ToLower(match)) {
+ list = append(list, flags.Completion{Item: name})
+ }
+ }
+
+ return list
+}
+
+func (cpn *ComponentAndPackageName) Complete(match string) []flags.Completion {
+
+ componentNames := getVolthaComponentNames()
+
+ // Return nil if no component names could be fetched
+ if len(componentNames) == 0 {
+ return nil
+ }
+
+ // Check to see if #was specified, and if so, we know we have
+ // to split component name and package
+ parts := strings.SplitN(match, "#", 2)
+
+ var list []flags.Completion
+ for _, name := range componentNames {
+ if strings.HasPrefix(name, parts[0]) {
+ list = append(list, flags.Completion{Item: name})
+ }
+ }
+
+ // If the possible completions > 1 then we have to stop here
+ // as we can't suggest packages
+ if len(parts) == 1 || len(list) > 1 {
+ return list
+ }
+
+ // Ok, we have a valid, unambiguous component name and there
+ // is a package separator, so lets try to expand the package
+ // and in this case we will replace the list we have so
+ // far with the new list
+ cname := list[0].Item
+ base := []flags.Completion{{Item: fmt.Sprintf("%s#%s", cname, parts[1])}}
+ packages, err := getPackageNames(cname)
+ if err != nil || len(packages) == 0 {
+ return base
+ }
+
+ list = []flags.Completion{}
+ for _, pname := range packages {
+ if strings.HasPrefix(pname, parts[1]) {
+ list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s", cname, pname)})
+ }
+ }
+
+ // if package part is present and still no match found based on prefix, user may be using
+ // short-hand notation for package name (last element of package path string e.g. kafka).
+ // Attempt prefix match against last element of package path (after the last / character)
+ if len(list) == 0 && len(parts[1]) >= 3 {
+ var mplist []string
+ for _, pname := range packages {
+ pnameparts := strings.Split(pname, "/")
+ if strings.HasPrefix(pnameparts[len(pnameparts)-1], parts[1]) {
+ mplist = append(mplist, pname)
+ }
+ }
+
+ // add to completion list if only a single match is found
+ if len(mplist) == 1 {
+ list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s", cname, mplist[0])})
+ }
+ }
+
+ // If the component name was expanded but package name match was not found, list will still be empty
+ // We should return entry with just component name auto-completed and package name unchanged.
+ if len(list) == 0 && cname != parts[0] {
+ // Returning 2 entries with <completed-component-name>#<package-part> as prefix
+ // Just 1 entry will auto-complete the argument
+ list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s1", cname, parts[1])})
+ list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s2", cname, parts[1])})
+ }
+
+ return list
+}
+
+func (ccpn *ConfiguredComponentAndPackageName) Complete(match string) []flags.Completion {
+
+ var list []flags.Completion
+
+ ProcessGlobalOptions()
+
+ log.SetAllLogLevel(log.FatalLevel)
+
+ client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), log.FatalLevel)
+ 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, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
+
+ ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+ defer cancel()
+
+ var componentNames []string
+ componentNames, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
+
+ // Return nil if no component names could be fetched
+ if err != nil || len(componentNames) == 0 {
+ return nil
+ }
+
+ // Check to see if #was specified, and if so, we know we have
+ // to split component name and package
+ parts := strings.SplitN(match, "#", 2)
+
+ for _, name := range componentNames {
+ if strings.HasPrefix(name, parts[0]) {
+ list = append(list, flags.Completion{Item: name})
+
+ // Handle scenario when one component is exact substring of other e.g. read-write-cor
+ // is substring of read-write-core (last e missing). Such a wrong component name
+ // can get configured during log level set operation
+ // In case of exact match of component name, use it if package part is present
+ if name == parts[0] && len(parts) == 2 {
+ list = []flags.Completion{{Item: name}}
+ break
+ }
+ }
+ }
+
+ // If the possible completions > 1 then we have to stop here
+ // as we can't suggest packages
+ if len(parts) == 1 || len(list) > 1 {
+ return list
+ }
+
+ // Ok, we have a valid, unambiguous component name and there
+ // is a package separator, so lets try to expand the package
+ // and in this case we will replace the list we have so
+ // far with the new list
+ cname := list[0].Item
+ base := []flags.Completion{{Item: fmt.Sprintf("%s#%s", cname, parts[1])}}
+
+ // Get list of packages configured for matching component name
+ logConfig := cm.InitComponentConfig(cname, config.ConfigTypeLogLevel)
+ logLevels, err1 := logConfig.RetrieveAll(ctx)
+ if err1 != nil || len(logLevels) == 0 {
+ return base
+ }
+
+ packages := make([]string, len(logLevels))
+ list = []flags.Completion{}
+ for pname := range logLevels {
+ pname = strings.ReplaceAll(pname, "#", "/")
+ packages = append(packages, pname)
+ if strings.HasPrefix(pname, parts[1]) {
+ list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s", cname, pname)})
+ }
+ }
+
+ // if package part is present and still no match found based on prefix, user may be using
+ // short-hand notation for package name (last element of package path string e.g. kafka).
+ // Attempt prefix match against last element of package path (after the last / character)
+ if len(list) == 0 && len(parts[1]) >= 3 {
+ var mplist []string
+ for _, pname := range packages {
+ pnameparts := strings.Split(pname, "/")
+ if strings.HasPrefix(pnameparts[len(pnameparts)-1], parts[1]) {
+ mplist = append(mplist, pname)
+ }
+ }
+
+ // add to completion list if only a single match is found
+ if len(mplist) == 1 {
+ list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s", cname, mplist[0])})
+ }
+ }
+
+ // If the component name was expanded but package name match was not found, list will still be empty
+ // We should return entry with just component name auto-completed and package name unchanged.
+ if len(list) == 0 && cname != parts[0] {
+ // Returning 2 entries with <completed-component-name>#<package-part> as prefix
+ // Just 1 entry will auto-complete the argument
+ list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s1", cname, parts[1])})
+ list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s2", cname, parts[1])})
+ }
+
+ return list
+}
+
+func (cn *ComponentName) Complete(match string) []flags.Completion {
+
+ componentNames := getVolthaComponentNames()
+
+ // Return nil if no component names could be fetched
+ if len(componentNames) == 0 {
+ return nil
+ }
+
+ var list []flags.Completion
+ for _, name := range componentNames {
+ if strings.HasPrefix(name, match) {
+ list = append(list, flags.Completion{Item: name})
+ }
+ }
+
+ return list
+}
+
+// Return nil if no component names could be fetched
+// processComponentListArgs stores the component name and package names given in command arguments to LogLevel
+// It checks the given argument has # key or not, if # is present then split the argument for # then stores first part as component name
+// and second part as package name
+func processComponentListArgs(Components []string) ([]model.LogLevel, error) {
+
+ var logLevelConfig []model.LogLevel
+
+ if len(Components) == 0 {
+ Components = append(Components, defaultComponentName)
+ }
+
+ for _, component := range Components {
+ logConfig := model.LogLevel{}
+ val := strings.SplitN(component, "#", 2)
+
+ if strings.Contains(val[0], "/") {
+ return nil, errors.New("the component name '" + val[0] + "' contains an invalid character '/'")
+ }
+
+ if len(val) > 1 {
+ if val[0] == defaultComponentName {
+ return nil, errors.New("global level doesn't support packageName")
+ }
+ logConfig.ComponentName = val[0]
+ logConfig.PackageName = strings.ReplaceAll(val[1], "/", "#")
+ } else {
+ logConfig.ComponentName = component
+ logConfig.PackageName = defaultPackageName
+ }
+ logLevelConfig = append(logLevelConfig, logConfig)
+ }
+ return logLevelConfig, nil
+}
+
+// This method set loglevel for components.
+// For example, using below command loglevel can be set for specific component with default packageName
+// voltctl loglevel set level <componentName>
+// For example, using below command loglevel can be set for specific component with specific packageName
+// voltctl loglevel set level <componentName#packageName>
+// For example, using below command loglevel can be set for more than one component for default package and other component for specific packageName
+// voltctl loglevel set level <componentName1#packageName> <componentName2>
+func (options *SetLogLevelOpts) Execute(args []string) error {
+ var (
+ logLevelConfig []model.LogLevel
+ err error
+ )
+ ProcessGlobalOptions()
+
+ log.SetAllLogLevel(log.FatalLevel)
+
+ if options.Args.Level != "" {
+ if _, err := log.StringToLogLevel(string(options.Args.Level)); err != nil {
+ return fmt.Errorf("Unknown log level '%s'. Allowed values are DEBUG, INFO, WARN, ERROR, FATAL", options.Args.Level)
+ }
+ }
+
+ logLevelConfig, err = processComponentListArgs(toStringArray(options.Args.Component))
+ if err != nil {
+ return fmt.Errorf(err.Error())
+ }
+
+ client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), 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, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
+
+ var output []LogLevelOutput
+
+ ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+ defer cancel()
+
+ for _, lConfig := range logLevelConfig {
+
+ logConfig := cm.InitComponentConfig(lConfig.ComponentName, config.ConfigTypeLogLevel)
+ err := logConfig.Save(ctx, lConfig.PackageName, strings.ToUpper(string(options.Args.Level)))
+ if err != nil {
+ output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Failure", Error: err.Error()})
+ } else {
+ output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Success"})
+ }
+
+ }
+
+ outputFormat := CharReplacer.Replace(options.Format)
+ if outputFormat == "" {
+ outputFormat = GetCommandOptionWithDefault("log-level-set", "format", DEFAULT_LOG_RESULT_FORMAT)
+ }
+ result := CommandResult{
+ Format: format.Format(outputFormat),
+ OutputAs: toOutputType(options.OutputAs),
+ NameLimit: options.NameLimit,
+ Data: output,
+ }
+
+ GenerateOutput(&result)
+ return nil
+}
+
+// This method list loglevel for components.
+// For example, using below command loglevel can be list for specific component
+// voltctl loglevel list <componentName>
+// For example, using below command loglevel can be list for all the components with all the packageName
+// voltctl loglevel list
+func (options *ListLogLevelsOpts) Execute(args []string) error {
+
+ var (
+ // Initialize to empty as opposed to nil so that -o json will
+ // display empty list and not null VOL-2742
+ data []model.LogLevel = []model.LogLevel{}
+ componentList []string
+ logLevelConfig map[string]string
+ err error
+ )
+ ProcessGlobalOptions()
+
+ log.SetAllLogLevel(log.FatalLevel)
+
+ client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), 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, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
+
+ ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+ defer cancel()
+
+ if len(options.Args.Component) == 0 {
+ componentList, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
+ if err != nil {
+ return fmt.Errorf("Unable to retrieve list of voltha components : %s ", err)
+ }
+ } else {
+ componentList = toStringArray(options.Args.Component)
+ }
+
+ for _, componentName := range componentList {
+ logConfig := cm.InitComponentConfig(componentName, config.ConfigTypeLogLevel)
+
+ logLevelConfig, err = logConfig.RetrieveAll(ctx)
+ if err != nil {
+ return fmt.Errorf("Unable to retrieve loglevel configuration for component %s : %s", componentName, err)
+ }
+
+ for packageName, level := range logLevelConfig {
+ logLevel := model.LogLevel{}
+ if packageName == "" {
+ continue
+ }
+
+ pName := strings.ReplaceAll(packageName, "#", "/")
+ logLevel.PopulateFrom(componentName, pName, level)
+ data = append(data, logLevel)
+ }
+ }
+
+ outputFormat := CharReplacer.Replace(options.Format)
+ if outputFormat == "" {
+ outputFormat = GetCommandOptionWithDefault("log-level-list", "format", DEFAULT_LOG_LEVELS_FORMAT)
+ }
+ orderBy := options.OrderBy
+ if orderBy == "" {
+ orderBy = GetCommandOptionWithDefault("log-level-list", "order", "")
+ }
+
+ result := CommandResult{
+ Format: format.Format(outputFormat),
+ Filter: options.Filter,
+ OrderBy: orderBy,
+ OutputAs: toOutputType(options.OutputAs),
+ NameLimit: options.NameLimit,
+ Data: data,
+ }
+ GenerateOutput(&result)
+ return nil
+}
+
+// This method clear loglevel for components.
+// For example, using below command loglevel can be clear for specific component with default packageName
+// voltctl loglevel clear <componentName>
+// For example, using below command loglevel can be clear for specific component with specific packageName
+// voltctl loglevel clear <componentName#packageName>
+func (options *ClearLogLevelsOpts) Execute(args []string) error {
+
+ var (
+ logLevelConfig []model.LogLevel
+ err error
+ )
+ ProcessGlobalOptions()
+
+ log.SetAllLogLevel(log.FatalLevel)
+
+ logLevelConfig, err = processComponentListArgs(toStringArray(options.Args.Component))
+ if err != nil {
+ return fmt.Errorf("%s", err)
+ }
+
+ client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), 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, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
+
+ var output []LogLevelOutput
+
+ ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+ defer cancel()
+
+ for _, lConfig := range logLevelConfig {
+
+ if lConfig.ComponentName == defaultComponentName {
+ return fmt.Errorf("The global default loglevel cannot be cleared.")
+ }
+
+ logConfig := cm.InitComponentConfig(lConfig.ComponentName, config.ConfigTypeLogLevel)
+
+ err := logConfig.Delete(ctx, lConfig.PackageName)
+ if err != nil {
+ output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Failure", Error: err.Error()})
+ } else {
+ output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Success"})
+ }
+ }
+
+ outputFormat := CharReplacer.Replace(options.Format)
+ if outputFormat == "" {
+ outputFormat = GetCommandOptionWithDefault("log-level-clear", "format", DEFAULT_LOG_RESULT_FORMAT)
+ }
+
+ result := CommandResult{
+ Format: format.Format(outputFormat),
+ OutputAs: toOutputType(options.OutputAs),
+ NameLimit: options.NameLimit,
+ Data: output,
+ }
+
+ GenerateOutput(&result)
+ return nil
+}
+
+// This method lists registered log packages for components.
+// For example, available log packages can be listed for specific component using below command
+// voltctl loglevel listpackage <componentName>
+// For example, available log packages can be listed for all the components using below command (omitting component name)
+// voltctl loglevel listpackage
+func (options *ListLogPackagesOpts) Execute(args []string) error {
+
+ var (
+ // Initialize to empty as opposed to nil so that -o json will
+ // display empty list and not null VOL-2742
+ data []model.LogLevel = []model.LogLevel{}
+ componentList []string
+ err error
+ )
+
+ ProcessGlobalOptions()
+
+ log.SetAllLogLevel(log.FatalLevel)
+
+ client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), 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, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
+
+ ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+ defer cancel()
+
+ if len(options.Args.Component) == 0 {
+ componentList, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
+ if err != nil {
+ return fmt.Errorf("Unable to retrieve list of voltha components : %s ", err)
+ }
+
+ // Include default global package as well when displaying packages for all components
+ logLevel := model.LogLevel{}
+ logLevel.PopulateFrom(defaultComponentName, defaultPackageName, "")
+ data = append(data, logLevel)
+ } else {
+ for _, name := range options.Args.Component {
+ componentList = append(componentList, string(name))
+ }
+ }
+
+ for _, componentName := range componentList {
+ componentMetadata := cm.InitComponentConfig(componentName, config.ConfigTypeMetadata)
+
+ value, err := componentMetadata.Retrieve(ctx, logPackagesListKey)
+ if err != nil || value == "" {
+ // Ignore any error in retrieval for log package list; some components may not store it
+ continue
+ }
+
+ var packageList []string
+ if err = json.Unmarshal([]byte(value), &packageList); err != nil {
+ continue
+ }
+
+ for _, packageName := range packageList {
+ logLevel := model.LogLevel{}
+ logLevel.PopulateFrom(componentName, packageName, "")
+ data = append(data, logLevel)
+ }
+ }
+
+ outputFormat := CharReplacer.Replace(options.Format)
+ if outputFormat == "" {
+ outputFormat = GetCommandOptionWithDefault("log-package-list", "format", DEFAULT_LOG_PACKAGES_FORMAT)
+ }
+ orderBy := options.OrderBy
+ if orderBy == "" {
+ orderBy = GetCommandOptionWithDefault("log-package-list", "order", "ComponentName,PackageName")
+ }
+
+ result := CommandResult{
+ Format: format.Format(outputFormat),
+ Filter: options.Filter,
+ OrderBy: orderBy,
+ OutputAs: toOutputType(options.OutputAs),
+ NameLimit: options.NameLimit,
+ Data: data,
+ }
+ GenerateOutput(&result)
+ return nil
+}
diff --git a/internal/pkg/commands/loglevel.go b/internal/pkg/commands/loglevel.go
deleted file mode 100644
index 30e0cae..0000000
--- a/internal/pkg/commands/loglevel.go
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * Copyright 2019-present Ciena Corporation
- *
- * 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 commands
-
-import (
- "context"
- "errors"
- "fmt"
- flags "github.com/jessevdk/go-flags"
- "github.com/opencord/voltctl/pkg/format"
- "github.com/opencord/voltctl/pkg/model"
- "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/log"
- "strings"
-)
-
-const (
- defaultComponentName = "global"
- defaultPackageName = "default"
-)
-
-// LogLevelOutput represents the output structure for the loglevel
-type LogLevelOutput struct {
- ComponentName string
- Status string
- Error string
-}
-
-// SetLogLevelOpts represents the supported CLI arguments for the loglevel set command
-type SetLogLevelOpts struct {
- OutputOptions
- Args struct {
- Level string
- Component []string
- } `positional-args:"yes" required:"yes"`
-}
-
-// ListLogLevelOpts represents the supported CLI arguments for the loglevel list command
-type ListLogLevelsOpts struct {
- ListOutputOptions
- Args struct {
- Component []string
- } `positional-args:"yes" required:"yes"`
-}
-
-// ClearLogLevelOpts represents the supported CLI arguments for the loglevel clear command
-type ClearLogLevelsOpts struct {
- OutputOptions
- Args struct {
- Component []string
- } `positional-args:"yes" required:"yes"`
-}
-
-// LogLevelOpts represents the loglevel commands
-type LogLevelOpts struct {
- SetLogLevel SetLogLevelOpts `command:"set"`
- ListLogLevels ListLogLevelsOpts `command:"list"`
- ClearLogLevels ClearLogLevelsOpts `command:"clear"`
-}
-
-var logLevelOpts = LogLevelOpts{}
-
-const (
- DEFAULT_LOGLEVELS_FORMAT = "table{{ .ComponentName }}\t{{.PackageName}}\t{{.Level}}"
- DEFAULT_LOGLEVEL_RESULT_FORMAT = "table{{ .ComponentName }}\t{{.Status}}\t{{.Error}}"
-)
-
-// RegisterLogLevelCommands is used to register set,list and clear loglevel of components
-func RegisterLogLevelCommands(parent *flags.Parser) {
- _, err := parent.AddCommand("loglevel", "loglevel commands", "list, set, clear log levels of components", &logLevelOpts)
- if err != nil {
- Error.Fatalf("Unable to register log level commands with voltctl command parser: %s", err.Error())
- }
-}
-
-// processComponentListArgs stores the component name and package names given in command arguments to LogLevel
-// It checks the given argument has # key or not, if # is present then split the argument for # then stores first part as component name
-// and second part as package name
-func processComponentListArgs(Components []string) ([]model.LogLevel, error) {
-
- var logLevelConfig []model.LogLevel
-
- if len(Components) == 0 {
- Components = append(Components, defaultComponentName)
- }
-
- for _, component := range Components {
- logConfig := model.LogLevel{}
- val := strings.SplitN(component, "#", 2)
-
- if strings.Contains(val[0], "/") {
- return nil, errors.New("the component name '" + val[0] + "' contains an invalid character '/'")
- }
-
- if len(val) > 1 {
- if val[0] == defaultComponentName {
- return nil, errors.New("global level doesn't support packageName")
- }
- logConfig.ComponentName = val[0]
- logConfig.PackageName = strings.ReplaceAll(val[1], "/", "#")
- } else {
- logConfig.ComponentName = component
- logConfig.PackageName = defaultPackageName
- }
- logLevelConfig = append(logLevelConfig, logConfig)
- }
- return logLevelConfig, nil
-}
-
-// This method set loglevel for components.
-// For example, using below command loglevel can be set for specific component with default packageName
-// voltctl loglevel set level <componentName>
-// For example, using below command loglevel can be set for specific component with specific packageName
-// voltctl loglevel set level <componentName#packageName>
-// For example, using below command loglevel can be set for more than one component for default package and other component for specific packageName
-// voltctl loglevel set level <componentName1#packageName> <componentName2>
-func (options *SetLogLevelOpts) Execute(args []string) error {
- var (
- logLevelConfig []model.LogLevel
- err error
- )
- ProcessGlobalOptions()
-
- log.SetAllLogLevel(log.FatalLevel)
-
- if options.Args.Level != "" {
- if _, err := log.StringToLogLevel(options.Args.Level); err != nil {
- return fmt.Errorf("Unknown log level '%s'. Allowed values are DEBUG, INFO, WARN, ERROR, FATAL", options.Args.Level)
- }
- }
-
- logLevelConfig, err = processComponentListArgs(options.Args.Component)
- if err != nil {
- return fmt.Errorf(err.Error())
- }
-
- client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), 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, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
-
- var output []LogLevelOutput
-
- ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
- defer cancel()
-
- for _, lConfig := range logLevelConfig {
-
- logConfig := cm.InitComponentConfig(lConfig.ComponentName, config.ConfigTypeLogLevel)
- err := logConfig.Save(ctx, lConfig.PackageName, strings.ToUpper(options.Args.Level))
- if err != nil {
- output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Failure", Error: err.Error()})
- } else {
- output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Success"})
- }
-
- }
-
- outputFormat := CharReplacer.Replace(options.Format)
- if outputFormat == "" {
- outputFormat = GetCommandOptionWithDefault("loglevel-set", "format", DEFAULT_LOGLEVEL_RESULT_FORMAT)
- }
- result := CommandResult{
- Format: format.Format(outputFormat),
- OutputAs: toOutputType(options.OutputAs),
- NameLimit: options.NameLimit,
- Data: output,
- }
-
- GenerateOutput(&result)
- return nil
-}
-
-// This method list loglevel for components.
-// For example, using below command loglevel can be list for specific component
-// voltctl loglevel list <componentName>
-// For example, using below command loglevel can be list for all the components with all the packageName
-// voltctl loglevel list
-func (options *ListLogLevelsOpts) Execute(args []string) error {
-
- var (
- // Initialize to empty as opposed to nil so that -o json will
- // display empty list and not null VOL-2742
- data []model.LogLevel = []model.LogLevel{}
- componentList []string
- logLevelConfig map[string]string
- err error
- )
- ProcessGlobalOptions()
-
- log.SetAllLogLevel(log.FatalLevel)
-
- client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), 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, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
-
- ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
- defer cancel()
-
- if len(options.Args.Component) == 0 {
- componentList, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
- if err != nil {
- return fmt.Errorf("Unable to retrieve list of voltha components : %s ", err)
- }
- } else {
- componentList = options.Args.Component
- }
-
- for _, componentName := range componentList {
- logConfig := cm.InitComponentConfig(componentName, config.ConfigTypeLogLevel)
-
- logLevelConfig, err = logConfig.RetrieveAll(ctx)
- if err != nil {
- return fmt.Errorf("Unable to retrieve loglevel configuration for component %s : %s", componentName, err)
- }
-
- for packageName, level := range logLevelConfig {
- logLevel := model.LogLevel{}
- if packageName == "" {
- continue
- }
-
- pName := strings.ReplaceAll(packageName, "#", "/")
- logLevel.PopulateFrom(componentName, pName, level)
- data = append(data, logLevel)
- }
- }
-
- outputFormat := CharReplacer.Replace(options.Format)
- if outputFormat == "" {
- outputFormat = GetCommandOptionWithDefault("loglevel-list", "format", DEFAULT_LOGLEVELS_FORMAT)
- }
- orderBy := options.OrderBy
- if orderBy == "" {
- orderBy = GetCommandOptionWithDefault("loglevel-list", "order", "")
- }
-
- result := CommandResult{
- Format: format.Format(outputFormat),
- Filter: options.Filter,
- OrderBy: orderBy,
- OutputAs: toOutputType(options.OutputAs),
- NameLimit: options.NameLimit,
- Data: data,
- }
- GenerateOutput(&result)
- return nil
-}
-
-// This method clear loglevel for components.
-// For example, using below command loglevel can be clear for specific component with default packageName
-// voltctl loglevel clear <componentName>
-// For example, using below command loglevel can be clear for specific component with specific packageName
-// voltctl loglevel clear <componentName#packageName>
-func (options *ClearLogLevelsOpts) Execute(args []string) error {
-
- var (
- logLevelConfig []model.LogLevel
- err error
- )
- ProcessGlobalOptions()
-
- log.SetAllLogLevel(log.FatalLevel)
-
- logLevelConfig, err = processComponentListArgs(options.Args.Component)
- if err != nil {
- return fmt.Errorf("%s", err)
- }
-
- client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), 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, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
-
- var output []LogLevelOutput
-
- ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
- defer cancel()
-
- for _, lConfig := range logLevelConfig {
-
- if lConfig.ComponentName == defaultComponentName {
- return fmt.Errorf("The global default loglevel cannot be cleared.")
- }
-
- logConfig := cm.InitComponentConfig(lConfig.ComponentName, config.ConfigTypeLogLevel)
-
- err := logConfig.Delete(ctx, lConfig.PackageName)
- if err != nil {
- output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Failure", Error: err.Error()})
- } else {
- output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Success"})
- }
- }
-
- outputFormat := CharReplacer.Replace(options.Format)
- if outputFormat == "" {
- outputFormat = GetCommandOptionWithDefault("loglevel-clear", "format", DEFAULT_LOGLEVEL_RESULT_FORMAT)
- }
-
- result := CommandResult{
- Format: format.Format(outputFormat),
- OutputAs: toOutputType(options.OutputAs),
- NameLimit: options.NameLimit,
- Data: output,
- }
-
- GenerateOutput(&result)
- return nil
-}
diff --git a/voltctl.config b/voltctl.config
index 13e1de5..da87203 100644
--- a/voltctl.config
+++ b/voltctl.config
@@ -1,9 +1,14 @@
apiVersion: v3
-server: voltha.voltha.svc.cluster.local:50555
+server: localhost:55555
+kafka: localhost:9092
+kvstore: localhost:2379
+tls:
+ useTls: false
+ caCert: ""
+ cert: ""
+ key: ""
+ verify: ""
grpc:
- timeout: 10s
-kvstore:
- kvstoreType: "etcd"
- kvstoretimeout: 5
- kvstorehost: "localhost"
- kvstoreport: 2379
+ timeout: 5m0s
+kvstoreconfig:
+ timeout: 5s
diff --git a/voltctl_command_options.config b/voltctl_command_options.config
index c474eb6..8c4afc3 100644
--- a/voltctl_command_options.config
+++ b/voltctl_command_options.config
@@ -23,5 +23,9 @@
component-list:
order: Component,Name,Id
-loglevel-list:
+log-level-list:
order: ComponentName,PackageName,Level
+
+log-package-list:
+ format: "table{{.ComponentName }}\t{{.PackageName}}"
+ order: ComponentName,PackageName