blob: fdd37391ea912d29c169478f4e3ba38f56c17458 [file] [log] [blame]
Girish Kumarb03dcee2020-04-14 11:48:15 +00001/*
2 * Copyright 2019-present Ciena Corporation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package commands
17
18import (
19 "context"
20 "encoding/json"
21 "errors"
22 "fmt"
23 "strings"
24
25 flags "github.com/jessevdk/go-flags"
26 "github.com/opencord/voltctl/pkg/format"
27 "github.com/opencord/voltctl/pkg/model"
28 "github.com/opencord/voltha-lib-go/v3/pkg/config"
29 "github.com/opencord/voltha-lib-go/v3/pkg/db/kvstore"
30 "github.com/opencord/voltha-lib-go/v3/pkg/log"
31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32 "k8s.io/client-go/kubernetes"
33 "k8s.io/client-go/tools/clientcmd"
34)
35
36const (
37 defaultComponentName = "global"
38 defaultPackageName = "default"
39 logPackagesListKey = "log_package_list" // kvstore key containing list of allowed log packages
40)
41
42// Custom Option representing <component-name>#<package-name> format (package is optional)
43// This is used by 'log level set' commands
44type ComponentAndPackageName string
45
46// Custom Option representing currently configured log configuration in <component-name>#<package-name> format (package is optional)
47// This is used by 'log level clear' commands
48type ConfiguredComponentAndPackageName string
49
50// Custom Option representing component-name. This is used by 'log level list' and 'log package list' commands
51type ComponentName string
52
53// Custom Option representing Log Level (one of debug, info, warn, error, fatal)
54type LevelName string
55
56// LogLevelOutput represents the output structure for the loglevel
57type LogLevelOutput struct {
58 ComponentName string
59 Status string
60 Error string
61}
62
63// SetLogLevelOpts represents the supported CLI arguments for the loglevel set command
64type SetLogLevelOpts struct {
65 OutputOptions
66 Args struct {
67 Level LevelName
68 Component []ComponentAndPackageName
69 } `positional-args:"yes" required:"yes"`
70}
71
72// ListLogLevelOpts represents the supported CLI arguments for the loglevel list command
73type ListLogLevelsOpts struct {
74 ListOutputOptions
75 Args struct {
76 Component []ComponentName
77 } `positional-args:"yes" required:"yes"`
78}
79
80// ClearLogLevelOpts represents the supported CLI arguments for the loglevel clear command
81type ClearLogLevelsOpts struct {
82 OutputOptions
83 Args struct {
84 Component []ConfiguredComponentAndPackageName
85 } `positional-args:"yes" required:"yes"`
86}
87
88// ListLogLevelOpts represents the supported CLI arguments for the loglevel list command
89type ListLogPackagesOpts struct {
90 ListOutputOptions
91 Args struct {
92 Component []ComponentName
93 } `positional-args:"yes" required:"yes"`
94}
95
96// LogPackageOpts represents the log package commands
97type LogPackageOpts struct {
98 ListLogPackages ListLogPackagesOpts `command:"list"`
99}
100
101// LogLevelOpts represents the log level commands
102type LogLevelOpts struct {
103 SetLogLevel SetLogLevelOpts `command:"set"`
104 ListLogLevels ListLogLevelsOpts `command:"list"`
105 ClearLogLevels ClearLogLevelsOpts `command:"clear"`
106}
107
108// LogOpts represents the log commands
109type LogOpts struct {
110 LogLevel LogLevelOpts `command:"level"`
111 LogPackage LogPackageOpts `command:"package"`
112}
113
114var logOpts = LogOpts{}
115
116const (
117 DEFAULT_LOG_LEVELS_FORMAT = "table{{ .ComponentName }}\t{{.PackageName}}\t{{.Level}}"
118 DEFAULT_LOG_PACKAGES_FORMAT = "table{{ .ComponentName }}\t{{.PackageName}}"
119 DEFAULT_LOG_RESULT_FORMAT = "table{{ .ComponentName }}\t{{.Status}}\t{{.Error}}"
120)
121
122func toStringArray(arg interface{}) []string {
123 var list []string
124 if cnl, ok := arg.([]ComponentName); ok {
125 for _, cn := range cnl {
126 list = append(list, string(cn))
127 }
128 } else if cpnl, ok := arg.([]ComponentAndPackageName); ok {
129 for _, cpn := range cpnl {
130 list = append(list, string(cpn))
131 }
132 } else if ccpnl, ok := arg.([]ConfiguredComponentAndPackageName); ok {
133 for _, ccpn := range ccpnl {
134 list = append(list, string(ccpn))
135 }
136 }
137
138 return list
139}
140
141// RegisterLogCommands is used to register log and its sub-commands e.g. level, package etc
142func RegisterLogCommands(parent *flags.Parser) {
143 _, err := parent.AddCommand("log", "log config commands", "list, set, clear log levels and list packages of components", &logOpts)
144 if err != nil {
145 Error.Fatalf("Unable to register log commands with voltctl command parser: %s", err.Error())
146 }
147}
148
149// Common method to get list of VOLTHA components using k8s API. Used for validation and auto-complete
150// Just return a blank list in case of any error
151func getVolthaComponentNames() []string {
152 var componentList []string
153
154 // use the current context in kubeconfig
155 config, err := clientcmd.BuildConfigFromFlags("", GlobalOptions.K8sConfig)
156 if err != nil {
157 // Ignore any error
158 return componentList
159 }
160
161 // create the clientset
162 clientset, err := kubernetes.NewForConfig(config)
163 if err != nil {
164 return componentList
165 }
166
167 pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{
168 LabelSelector: "app.kubernetes.io/part-of=voltha",
169 })
170 if err != nil {
171 return componentList
172 }
173
174 for _, pod := range pods.Items {
175 componentList = append(componentList, pod.ObjectMeta.Labels["app.kubernetes.io/name"])
176 }
177
178 return componentList
179}
180
181func getPackageNames(componentName string) ([]string, error) {
182 list := []string{defaultPackageName}
183
184 ProcessGlobalOptions()
185
186 log.SetAllLogLevel(log.FatalLevel)
187
188 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), log.FatalLevel)
189 if err != nil {
190 return nil, fmt.Errorf("Unable to create kvstore client %s", err)
191 }
192 defer client.Close()
193
194 // Already error checked during option processing
195 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
196 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
197
198 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
199 defer cancel()
200
201 componentMetadata := cm.InitComponentConfig(componentName, config.ConfigTypeMetadata)
202
203 value, err := componentMetadata.Retrieve(ctx, logPackagesListKey)
204 if err != nil || value == "" {
205 return list, nil
206 }
207
208 var packageList []string
209 if err = json.Unmarshal([]byte(value), &packageList); err != nil {
210 return list, nil
211 }
212
213 list = append(list, packageList...)
214
215 return list, nil
216}
217
218func (ln *LevelName) Complete(match string) []flags.Completion {
219 levels := []string{"debug", "info", "warn", "error", "fatal"}
220
221 var list []flags.Completion
222 for _, name := range levels {
223 if strings.HasPrefix(name, strings.ToLower(match)) {
224 list = append(list, flags.Completion{Item: name})
225 }
226 }
227
228 return list
229}
230
231func (cpn *ComponentAndPackageName) Complete(match string) []flags.Completion {
232
233 componentNames := getVolthaComponentNames()
234
235 // Return nil if no component names could be fetched
236 if len(componentNames) == 0 {
237 return nil
238 }
239
240 // Check to see if #was specified, and if so, we know we have
241 // to split component name and package
242 parts := strings.SplitN(match, "#", 2)
243
244 var list []flags.Completion
245 for _, name := range componentNames {
246 if strings.HasPrefix(name, parts[0]) {
247 list = append(list, flags.Completion{Item: name})
248 }
249 }
250
251 // If the possible completions > 1 then we have to stop here
252 // as we can't suggest packages
253 if len(parts) == 1 || len(list) > 1 {
254 return list
255 }
256
257 // Ok, we have a valid, unambiguous component name and there
258 // is a package separator, so lets try to expand the package
259 // and in this case we will replace the list we have so
260 // far with the new list
261 cname := list[0].Item
262 base := []flags.Completion{{Item: fmt.Sprintf("%s#%s", cname, parts[1])}}
263 packages, err := getPackageNames(cname)
264 if err != nil || len(packages) == 0 {
265 return base
266 }
267
268 list = []flags.Completion{}
269 for _, pname := range packages {
270 if strings.HasPrefix(pname, parts[1]) {
271 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s", cname, pname)})
272 }
273 }
274
275 // if package part is present and still no match found based on prefix, user may be using
276 // short-hand notation for package name (last element of package path string e.g. kafka).
277 // Attempt prefix match against last element of package path (after the last / character)
278 if len(list) == 0 && len(parts[1]) >= 3 {
279 var mplist []string
280 for _, pname := range packages {
281 pnameparts := strings.Split(pname, "/")
282 if strings.HasPrefix(pnameparts[len(pnameparts)-1], parts[1]) {
283 mplist = append(mplist, pname)
284 }
285 }
286
287 // add to completion list if only a single match is found
288 if len(mplist) == 1 {
289 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s", cname, mplist[0])})
290 }
291 }
292
293 // If the component name was expanded but package name match was not found, list will still be empty
294 // We should return entry with just component name auto-completed and package name unchanged.
295 if len(list) == 0 && cname != parts[0] {
296 // Returning 2 entries with <completed-component-name>#<package-part> as prefix
297 // Just 1 entry will auto-complete the argument
298 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s1", cname, parts[1])})
299 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s2", cname, parts[1])})
300 }
301
302 return list
303}
304
305func (ccpn *ConfiguredComponentAndPackageName) Complete(match string) []flags.Completion {
306
307 var list []flags.Completion
308
309 ProcessGlobalOptions()
310
311 log.SetAllLogLevel(log.FatalLevel)
312
313 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), log.FatalLevel)
314 if err != nil {
315 return list
316 }
317 defer client.Close()
318
319 // Already error checked during option processing
320 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
321 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
322
323 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
324 defer cancel()
325
326 var componentNames []string
327 componentNames, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
328
329 // Return nil if no component names could be fetched
330 if err != nil || len(componentNames) == 0 {
331 return nil
332 }
333
334 // Check to see if #was specified, and if so, we know we have
335 // to split component name and package
336 parts := strings.SplitN(match, "#", 2)
337
338 for _, name := range componentNames {
339 if strings.HasPrefix(name, parts[0]) {
340 list = append(list, flags.Completion{Item: name})
341
342 // Handle scenario when one component is exact substring of other e.g. read-write-cor
343 // is substring of read-write-core (last e missing). Such a wrong component name
344 // can get configured during log level set operation
345 // In case of exact match of component name, use it if package part is present
346 if name == parts[0] && len(parts) == 2 {
347 list = []flags.Completion{{Item: name}}
348 break
349 }
350 }
351 }
352
353 // If the possible completions > 1 then we have to stop here
354 // as we can't suggest packages
355 if len(parts) == 1 || len(list) > 1 {
356 return list
357 }
358
359 // Ok, we have a valid, unambiguous component name and there
360 // is a package separator, so lets try to expand the package
361 // and in this case we will replace the list we have so
362 // far with the new list
363 cname := list[0].Item
364 base := []flags.Completion{{Item: fmt.Sprintf("%s#%s", cname, parts[1])}}
365
366 // Get list of packages configured for matching component name
367 logConfig := cm.InitComponentConfig(cname, config.ConfigTypeLogLevel)
368 logLevels, err1 := logConfig.RetrieveAll(ctx)
369 if err1 != nil || len(logLevels) == 0 {
370 return base
371 }
372
373 packages := make([]string, len(logLevels))
374 list = []flags.Completion{}
375 for pname := range logLevels {
376 pname = strings.ReplaceAll(pname, "#", "/")
377 packages = append(packages, pname)
378 if strings.HasPrefix(pname, parts[1]) {
379 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s", cname, pname)})
380 }
381 }
382
383 // if package part is present and still no match found based on prefix, user may be using
384 // short-hand notation for package name (last element of package path string e.g. kafka).
385 // Attempt prefix match against last element of package path (after the last / character)
386 if len(list) == 0 && len(parts[1]) >= 3 {
387 var mplist []string
388 for _, pname := range packages {
389 pnameparts := strings.Split(pname, "/")
390 if strings.HasPrefix(pnameparts[len(pnameparts)-1], parts[1]) {
391 mplist = append(mplist, pname)
392 }
393 }
394
395 // add to completion list if only a single match is found
396 if len(mplist) == 1 {
397 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s", cname, mplist[0])})
398 }
399 }
400
401 // If the component name was expanded but package name match was not found, list will still be empty
402 // We should return entry with just component name auto-completed and package name unchanged.
403 if len(list) == 0 && cname != parts[0] {
404 // Returning 2 entries with <completed-component-name>#<package-part> as prefix
405 // Just 1 entry will auto-complete the argument
406 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s1", cname, parts[1])})
407 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s2", cname, parts[1])})
408 }
409
410 return list
411}
412
413func (cn *ComponentName) Complete(match string) []flags.Completion {
414
415 componentNames := getVolthaComponentNames()
416
417 // Return nil if no component names could be fetched
418 if len(componentNames) == 0 {
419 return nil
420 }
421
422 var list []flags.Completion
423 for _, name := range componentNames {
424 if strings.HasPrefix(name, match) {
425 list = append(list, flags.Completion{Item: name})
426 }
427 }
428
429 return list
430}
431
432// Return nil if no component names could be fetched
433// processComponentListArgs stores the component name and package names given in command arguments to LogLevel
434// It checks the given argument has # key or not, if # is present then split the argument for # then stores first part as component name
435// and second part as package name
436func processComponentListArgs(Components []string) ([]model.LogLevel, error) {
437
438 var logLevelConfig []model.LogLevel
439
440 if len(Components) == 0 {
441 Components = append(Components, defaultComponentName)
442 }
443
444 for _, component := range Components {
445 logConfig := model.LogLevel{}
446 val := strings.SplitN(component, "#", 2)
447
448 if strings.Contains(val[0], "/") {
449 return nil, errors.New("the component name '" + val[0] + "' contains an invalid character '/'")
450 }
451
452 if len(val) > 1 {
453 if val[0] == defaultComponentName {
454 return nil, errors.New("global level doesn't support packageName")
455 }
456 logConfig.ComponentName = val[0]
457 logConfig.PackageName = strings.ReplaceAll(val[1], "/", "#")
458 } else {
459 logConfig.ComponentName = component
460 logConfig.PackageName = defaultPackageName
461 }
462 logLevelConfig = append(logLevelConfig, logConfig)
463 }
464 return logLevelConfig, nil
465}
466
467// This method set loglevel for components.
468// For example, using below command loglevel can be set for specific component with default packageName
469// voltctl loglevel set level <componentName>
470// For example, using below command loglevel can be set for specific component with specific packageName
471// voltctl loglevel set level <componentName#packageName>
472// For example, using below command loglevel can be set for more than one component for default package and other component for specific packageName
473// voltctl loglevel set level <componentName1#packageName> <componentName2>
474func (options *SetLogLevelOpts) Execute(args []string) error {
475 var (
476 logLevelConfig []model.LogLevel
477 err error
478 )
479 ProcessGlobalOptions()
480
481 log.SetAllLogLevel(log.FatalLevel)
482
483 if options.Args.Level != "" {
484 if _, err := log.StringToLogLevel(string(options.Args.Level)); err != nil {
485 return fmt.Errorf("Unknown log level '%s'. Allowed values are DEBUG, INFO, WARN, ERROR, FATAL", options.Args.Level)
486 }
487 }
488
489 logLevelConfig, err = processComponentListArgs(toStringArray(options.Args.Component))
490 if err != nil {
491 return fmt.Errorf(err.Error())
492 }
493
494 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), log.FatalLevel)
495 if err != nil {
496 return fmt.Errorf("Unable to create kvstore client %s", err)
497 }
498 defer client.Close()
499
500 // Already error checked during option processing
501 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
502 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
503
504 var output []LogLevelOutput
505
506 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
507 defer cancel()
508
509 for _, lConfig := range logLevelConfig {
510
511 logConfig := cm.InitComponentConfig(lConfig.ComponentName, config.ConfigTypeLogLevel)
512 err := logConfig.Save(ctx, lConfig.PackageName, strings.ToUpper(string(options.Args.Level)))
513 if err != nil {
514 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Failure", Error: err.Error()})
515 } else {
516 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Success"})
517 }
518
519 }
520
521 outputFormat := CharReplacer.Replace(options.Format)
522 if outputFormat == "" {
523 outputFormat = GetCommandOptionWithDefault("log-level-set", "format", DEFAULT_LOG_RESULT_FORMAT)
524 }
525 result := CommandResult{
526 Format: format.Format(outputFormat),
527 OutputAs: toOutputType(options.OutputAs),
528 NameLimit: options.NameLimit,
529 Data: output,
530 }
531
532 GenerateOutput(&result)
533 return nil
534}
535
536// This method list loglevel for components.
537// For example, using below command loglevel can be list for specific component
538// voltctl loglevel list <componentName>
539// For example, using below command loglevel can be list for all the components with all the packageName
540// voltctl loglevel list
541func (options *ListLogLevelsOpts) Execute(args []string) error {
542
543 var (
544 // Initialize to empty as opposed to nil so that -o json will
545 // display empty list and not null VOL-2742
546 data []model.LogLevel = []model.LogLevel{}
547 componentList []string
548 logLevelConfig map[string]string
549 err error
550 )
551 ProcessGlobalOptions()
552
553 log.SetAllLogLevel(log.FatalLevel)
554
555 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), log.FatalLevel)
556 if err != nil {
557 return fmt.Errorf("Unable to create kvstore client %s", err)
558 }
559 defer client.Close()
560
561 // Already error checked during option processing
562 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
563 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
564
565 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
566 defer cancel()
567
568 if len(options.Args.Component) == 0 {
569 componentList, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
570 if err != nil {
571 return fmt.Errorf("Unable to retrieve list of voltha components : %s ", err)
572 }
573 } else {
574 componentList = toStringArray(options.Args.Component)
575 }
576
577 for _, componentName := range componentList {
578 logConfig := cm.InitComponentConfig(componentName, config.ConfigTypeLogLevel)
579
580 logLevelConfig, err = logConfig.RetrieveAll(ctx)
581 if err != nil {
582 return fmt.Errorf("Unable to retrieve loglevel configuration for component %s : %s", componentName, err)
583 }
584
585 for packageName, level := range logLevelConfig {
586 logLevel := model.LogLevel{}
587 if packageName == "" {
588 continue
589 }
590
591 pName := strings.ReplaceAll(packageName, "#", "/")
592 logLevel.PopulateFrom(componentName, pName, level)
593 data = append(data, logLevel)
594 }
595 }
596
597 outputFormat := CharReplacer.Replace(options.Format)
598 if outputFormat == "" {
599 outputFormat = GetCommandOptionWithDefault("log-level-list", "format", DEFAULT_LOG_LEVELS_FORMAT)
600 }
601 orderBy := options.OrderBy
602 if orderBy == "" {
603 orderBy = GetCommandOptionWithDefault("log-level-list", "order", "")
604 }
605
606 result := CommandResult{
607 Format: format.Format(outputFormat),
608 Filter: options.Filter,
609 OrderBy: orderBy,
610 OutputAs: toOutputType(options.OutputAs),
611 NameLimit: options.NameLimit,
612 Data: data,
613 }
614 GenerateOutput(&result)
615 return nil
616}
617
618// This method clear loglevel for components.
619// For example, using below command loglevel can be clear for specific component with default packageName
620// voltctl loglevel clear <componentName>
621// For example, using below command loglevel can be clear for specific component with specific packageName
622// voltctl loglevel clear <componentName#packageName>
623func (options *ClearLogLevelsOpts) Execute(args []string) error {
624
625 var (
626 logLevelConfig []model.LogLevel
627 err error
628 )
629 ProcessGlobalOptions()
630
631 log.SetAllLogLevel(log.FatalLevel)
632
633 logLevelConfig, err = processComponentListArgs(toStringArray(options.Args.Component))
634 if err != nil {
635 return fmt.Errorf("%s", err)
636 }
637
638 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), log.FatalLevel)
639 if err != nil {
640 return fmt.Errorf("Unable to create kvstore client %s", err)
641 }
642 defer client.Close()
643
644 // Already error checked during option processing
645 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
646 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
647
648 var output []LogLevelOutput
649
650 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
651 defer cancel()
652
653 for _, lConfig := range logLevelConfig {
654
655 if lConfig.ComponentName == defaultComponentName {
656 return fmt.Errorf("The global default loglevel cannot be cleared.")
657 }
658
659 logConfig := cm.InitComponentConfig(lConfig.ComponentName, config.ConfigTypeLogLevel)
660
661 err := logConfig.Delete(ctx, lConfig.PackageName)
662 if err != nil {
663 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Failure", Error: err.Error()})
664 } else {
665 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Success"})
666 }
667 }
668
669 outputFormat := CharReplacer.Replace(options.Format)
670 if outputFormat == "" {
671 outputFormat = GetCommandOptionWithDefault("log-level-clear", "format", DEFAULT_LOG_RESULT_FORMAT)
672 }
673
674 result := CommandResult{
675 Format: format.Format(outputFormat),
676 OutputAs: toOutputType(options.OutputAs),
677 NameLimit: options.NameLimit,
678 Data: output,
679 }
680
681 GenerateOutput(&result)
682 return nil
683}
684
685// This method lists registered log packages for components.
686// For example, available log packages can be listed for specific component using below command
687// voltctl loglevel listpackage <componentName>
688// For example, available log packages can be listed for all the components using below command (omitting component name)
689// voltctl loglevel listpackage
690func (options *ListLogPackagesOpts) Execute(args []string) error {
691
692 var (
693 // Initialize to empty as opposed to nil so that -o json will
694 // display empty list and not null VOL-2742
695 data []model.LogLevel = []model.LogLevel{}
696 componentList []string
697 err error
698 )
699
700 ProcessGlobalOptions()
701
702 log.SetAllLogLevel(log.FatalLevel)
703
704 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), log.FatalLevel)
705 if err != nil {
706 return fmt.Errorf("Unable to create kvstore client %s", err)
707 }
708 defer client.Close()
709
710 // Already error checked during option processing
711 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
712 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
713
714 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
715 defer cancel()
716
717 if len(options.Args.Component) == 0 {
718 componentList, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
719 if err != nil {
720 return fmt.Errorf("Unable to retrieve list of voltha components : %s ", err)
721 }
722
723 // Include default global package as well when displaying packages for all components
724 logLevel := model.LogLevel{}
725 logLevel.PopulateFrom(defaultComponentName, defaultPackageName, "")
726 data = append(data, logLevel)
727 } else {
728 for _, name := range options.Args.Component {
729 componentList = append(componentList, string(name))
730 }
731 }
732
733 for _, componentName := range componentList {
734 componentMetadata := cm.InitComponentConfig(componentName, config.ConfigTypeMetadata)
735
736 value, err := componentMetadata.Retrieve(ctx, logPackagesListKey)
737 if err != nil || value == "" {
738 // Ignore any error in retrieval for log package list; some components may not store it
739 continue
740 }
741
742 var packageList []string
743 if err = json.Unmarshal([]byte(value), &packageList); err != nil {
744 continue
745 }
746
747 for _, packageName := range packageList {
748 logLevel := model.LogLevel{}
749 logLevel.PopulateFrom(componentName, packageName, "")
750 data = append(data, logLevel)
751 }
752 }
753
754 outputFormat := CharReplacer.Replace(options.Format)
755 if outputFormat == "" {
756 outputFormat = GetCommandOptionWithDefault("log-package-list", "format", DEFAULT_LOG_PACKAGES_FORMAT)
757 }
758 orderBy := options.OrderBy
759 if orderBy == "" {
760 orderBy = GetCommandOptionWithDefault("log-package-list", "order", "ComponentName,PackageName")
761 }
762
763 result := CommandResult{
764 Format: format.Format(outputFormat),
765 Filter: options.Filter,
766 OrderBy: orderBy,
767 OutputAs: toOutputType(options.OutputAs),
768 NameLimit: options.NameLimit,
769 Data: data,
770 }
771 GenerateOutput(&result)
772 return nil
773}