[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