blob: d755d86db04f108f83dc23756d6cc92751c3df7f [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
Girish Kumarda415372020-04-21 15:37:13 +000059 PackageName string
Girish Kumarb03dcee2020-04-14 11:48:15 +000060 Status string
61 Error string
62}
63
64// SetLogLevelOpts represents the supported CLI arguments for the loglevel set command
65type SetLogLevelOpts struct {
66 OutputOptions
67 Args struct {
68 Level LevelName
69 Component []ComponentAndPackageName
70 } `positional-args:"yes" required:"yes"`
71}
72
73// ListLogLevelOpts represents the supported CLI arguments for the loglevel list command
74type ListLogLevelsOpts struct {
75 ListOutputOptions
76 Args struct {
77 Component []ComponentName
78 } `positional-args:"yes" required:"yes"`
79}
80
81// ClearLogLevelOpts represents the supported CLI arguments for the loglevel clear command
82type ClearLogLevelsOpts struct {
83 OutputOptions
84 Args struct {
85 Component []ConfiguredComponentAndPackageName
86 } `positional-args:"yes" required:"yes"`
87}
88
89// ListLogLevelOpts represents the supported CLI arguments for the loglevel list command
90type ListLogPackagesOpts struct {
91 ListOutputOptions
92 Args struct {
93 Component []ComponentName
94 } `positional-args:"yes" required:"yes"`
95}
96
97// LogPackageOpts represents the log package commands
98type LogPackageOpts struct {
99 ListLogPackages ListLogPackagesOpts `command:"list"`
100}
101
102// LogLevelOpts represents the log level commands
103type LogLevelOpts struct {
104 SetLogLevel SetLogLevelOpts `command:"set"`
105 ListLogLevels ListLogLevelsOpts `command:"list"`
106 ClearLogLevels ClearLogLevelsOpts `command:"clear"`
107}
108
109// LogOpts represents the log commands
110type LogOpts struct {
111 LogLevel LogLevelOpts `command:"level"`
112 LogPackage LogPackageOpts `command:"package"`
113}
114
115var logOpts = LogOpts{}
116
117const (
118 DEFAULT_LOG_LEVELS_FORMAT = "table{{ .ComponentName }}\t{{.PackageName}}\t{{.Level}}"
119 DEFAULT_LOG_PACKAGES_FORMAT = "table{{ .ComponentName }}\t{{.PackageName}}"
Girish Kumarda415372020-04-21 15:37:13 +0000120 DEFAULT_LOG_RESULT_FORMAT = "table{{ .ComponentName }}\t{{.PackageName}}\t{{.Status}}\t{{.Error}}"
Girish Kumarb03dcee2020-04-14 11:48:15 +0000121)
122
123func toStringArray(arg interface{}) []string {
124 var list []string
125 if cnl, ok := arg.([]ComponentName); ok {
126 for _, cn := range cnl {
127 list = append(list, string(cn))
128 }
129 } else if cpnl, ok := arg.([]ComponentAndPackageName); ok {
130 for _, cpn := range cpnl {
131 list = append(list, string(cpn))
132 }
133 } else if ccpnl, ok := arg.([]ConfiguredComponentAndPackageName); ok {
134 for _, ccpn := range ccpnl {
135 list = append(list, string(ccpn))
136 }
137 }
138
139 return list
140}
141
142// RegisterLogCommands is used to register log and its sub-commands e.g. level, package etc
143func RegisterLogCommands(parent *flags.Parser) {
144 _, err := parent.AddCommand("log", "log config commands", "list, set, clear log levels and list packages of components", &logOpts)
145 if err != nil {
146 Error.Fatalf("Unable to register log commands with voltctl command parser: %s", err.Error())
147 }
148}
149
150// Common method to get list of VOLTHA components using k8s API. Used for validation and auto-complete
151// Just return a blank list in case of any error
152func getVolthaComponentNames() []string {
153 var componentList []string
154
155 // use the current context in kubeconfig
156 config, err := clientcmd.BuildConfigFromFlags("", GlobalOptions.K8sConfig)
157 if err != nil {
158 // Ignore any error
159 return componentList
160 }
161
162 // create the clientset
163 clientset, err := kubernetes.NewForConfig(config)
164 if err != nil {
165 return componentList
166 }
167
168 pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{
169 LabelSelector: "app.kubernetes.io/part-of=voltha",
170 })
171 if err != nil {
172 return componentList
173 }
174
175 for _, pod := range pods.Items {
176 componentList = append(componentList, pod.ObjectMeta.Labels["app.kubernetes.io/name"])
177 }
178
179 return componentList
180}
181
Girish Kumarda415372020-04-21 15:37:13 +0000182// Method to get list of allowed Package Names for a given component. This list
183// is saved into etcd kvstore by each active component at startup as a json array
Girish Kumarb03dcee2020-04-14 11:48:15 +0000184func getPackageNames(componentName string) ([]string, error) {
185 list := []string{defaultPackageName}
186
187 ProcessGlobalOptions()
188
189 log.SetAllLogLevel(log.FatalLevel)
190
191 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), log.FatalLevel)
192 if err != nil {
193 return nil, fmt.Errorf("Unable to create kvstore client %s", err)
194 }
195 defer client.Close()
196
197 // Already error checked during option processing
198 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
199 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
200
201 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
202 defer cancel()
203
204 componentMetadata := cm.InitComponentConfig(componentName, config.ConfigTypeMetadata)
205
206 value, err := componentMetadata.Retrieve(ctx, logPackagesListKey)
207 if err != nil || value == "" {
208 return list, nil
209 }
210
211 var packageList []string
212 if err = json.Unmarshal([]byte(value), &packageList); err != nil {
213 return list, nil
214 }
215
216 list = append(list, packageList...)
217
218 return list, nil
219}
220
221func (ln *LevelName) Complete(match string) []flags.Completion {
222 levels := []string{"debug", "info", "warn", "error", "fatal"}
223
224 var list []flags.Completion
225 for _, name := range levels {
226 if strings.HasPrefix(name, strings.ToLower(match)) {
227 list = append(list, flags.Completion{Item: name})
228 }
229 }
230
231 return list
232}
233
234func (cpn *ComponentAndPackageName) Complete(match string) []flags.Completion {
235
236 componentNames := getVolthaComponentNames()
237
238 // Return nil if no component names could be fetched
239 if len(componentNames) == 0 {
240 return nil
241 }
242
243 // Check to see if #was specified, and if so, we know we have
244 // to split component name and package
245 parts := strings.SplitN(match, "#", 2)
246
247 var list []flags.Completion
248 for _, name := range componentNames {
249 if strings.HasPrefix(name, parts[0]) {
250 list = append(list, flags.Completion{Item: name})
251 }
252 }
253
254 // If the possible completions > 1 then we have to stop here
255 // as we can't suggest packages
256 if len(parts) == 1 || len(list) > 1 {
257 return list
258 }
259
260 // Ok, we have a valid, unambiguous component name and there
261 // is a package separator, so lets try to expand the package
262 // and in this case we will replace the list we have so
263 // far with the new list
264 cname := list[0].Item
265 base := []flags.Completion{{Item: fmt.Sprintf("%s#%s", cname, parts[1])}}
266 packages, err := getPackageNames(cname)
267 if err != nil || len(packages) == 0 {
268 return base
269 }
270
271 list = []flags.Completion{}
272 for _, pname := range packages {
273 if strings.HasPrefix(pname, parts[1]) {
274 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s", cname, pname)})
275 }
276 }
277
278 // if package part is present and still no match found based on prefix, user may be using
279 // short-hand notation for package name (last element of package path string e.g. kafka).
280 // Attempt prefix match against last element of package path (after the last / character)
281 if len(list) == 0 && len(parts[1]) >= 3 {
282 var mplist []string
283 for _, pname := range packages {
284 pnameparts := strings.Split(pname, "/")
285 if strings.HasPrefix(pnameparts[len(pnameparts)-1], parts[1]) {
286 mplist = append(mplist, pname)
287 }
288 }
289
290 // add to completion list if only a single match is found
291 if len(mplist) == 1 {
292 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s", cname, mplist[0])})
293 }
294 }
295
296 // If the component name was expanded but package name match was not found, list will still be empty
297 // We should return entry with just component name auto-completed and package name unchanged.
298 if len(list) == 0 && cname != parts[0] {
299 // Returning 2 entries with <completed-component-name>#<package-part> as prefix
300 // Just 1 entry will auto-complete the argument
301 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s1", cname, parts[1])})
302 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s2", cname, parts[1])})
303 }
304
305 return list
306}
307
308func (ccpn *ConfiguredComponentAndPackageName) Complete(match string) []flags.Completion {
309
310 var list []flags.Completion
311
312 ProcessGlobalOptions()
313
314 log.SetAllLogLevel(log.FatalLevel)
315
316 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), log.FatalLevel)
317 if err != nil {
318 return list
319 }
320 defer client.Close()
321
322 // Already error checked during option processing
323 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
324 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
325
326 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
327 defer cancel()
328
329 var componentNames []string
330 componentNames, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
331
332 // Return nil if no component names could be fetched
333 if err != nil || len(componentNames) == 0 {
334 return nil
335 }
336
337 // Check to see if #was specified, and if so, we know we have
338 // to split component name and package
339 parts := strings.SplitN(match, "#", 2)
340
341 for _, name := range componentNames {
342 if strings.HasPrefix(name, parts[0]) {
343 list = append(list, flags.Completion{Item: name})
344
345 // Handle scenario when one component is exact substring of other e.g. read-write-cor
346 // is substring of read-write-core (last e missing). Such a wrong component name
347 // can get configured during log level set operation
348 // In case of exact match of component name, use it if package part is present
349 if name == parts[0] && len(parts) == 2 {
350 list = []flags.Completion{{Item: name}}
351 break
352 }
353 }
354 }
355
356 // If the possible completions > 1 then we have to stop here
357 // as we can't suggest packages
358 if len(parts) == 1 || len(list) > 1 {
359 return list
360 }
361
362 // Ok, we have a valid, unambiguous component name and there
363 // is a package separator, so lets try to expand the package
364 // and in this case we will replace the list we have so
365 // far with the new list
366 cname := list[0].Item
367 base := []flags.Completion{{Item: fmt.Sprintf("%s#%s", cname, parts[1])}}
368
369 // Get list of packages configured for matching component name
370 logConfig := cm.InitComponentConfig(cname, config.ConfigTypeLogLevel)
371 logLevels, err1 := logConfig.RetrieveAll(ctx)
372 if err1 != nil || len(logLevels) == 0 {
373 return base
374 }
375
376 packages := make([]string, len(logLevels))
377 list = []flags.Completion{}
378 for pname := range logLevels {
379 pname = strings.ReplaceAll(pname, "#", "/")
380 packages = append(packages, pname)
381 if strings.HasPrefix(pname, parts[1]) {
382 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s", cname, pname)})
383 }
384 }
385
386 // if package part is present and still no match found based on prefix, user may be using
387 // short-hand notation for package name (last element of package path string e.g. kafka).
388 // Attempt prefix match against last element of package path (after the last / character)
389 if len(list) == 0 && len(parts[1]) >= 3 {
390 var mplist []string
391 for _, pname := range packages {
392 pnameparts := strings.Split(pname, "/")
393 if strings.HasPrefix(pnameparts[len(pnameparts)-1], parts[1]) {
394 mplist = append(mplist, pname)
395 }
396 }
397
398 // add to completion list if only a single match is found
399 if len(mplist) == 1 {
400 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s", cname, mplist[0])})
401 }
402 }
403
404 // If the component name was expanded but package name match was not found, list will still be empty
405 // We should return entry with just component name auto-completed and package name unchanged.
406 if len(list) == 0 && cname != parts[0] {
407 // Returning 2 entries with <completed-component-name>#<package-part> as prefix
408 // Just 1 entry will auto-complete the argument
409 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s1", cname, parts[1])})
410 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s2", cname, parts[1])})
411 }
412
413 return list
414}
415
416func (cn *ComponentName) Complete(match string) []flags.Completion {
417
418 componentNames := getVolthaComponentNames()
419
420 // Return nil if no component names could be fetched
421 if len(componentNames) == 0 {
422 return nil
423 }
424
425 var list []flags.Completion
426 for _, name := range componentNames {
427 if strings.HasPrefix(name, match) {
428 list = append(list, flags.Completion{Item: name})
429 }
430 }
431
432 return list
433}
434
435// Return nil if no component names could be fetched
436// processComponentListArgs stores the component name and package names given in command arguments to LogLevel
437// It checks the given argument has # key or not, if # is present then split the argument for # then stores first part as component name
438// and second part as package name
439func processComponentListArgs(Components []string) ([]model.LogLevel, error) {
440
441 var logLevelConfig []model.LogLevel
442
443 if len(Components) == 0 {
444 Components = append(Components, defaultComponentName)
445 }
446
447 for _, component := range Components {
448 logConfig := model.LogLevel{}
449 val := strings.SplitN(component, "#", 2)
450
451 if strings.Contains(val[0], "/") {
452 return nil, errors.New("the component name '" + val[0] + "' contains an invalid character '/'")
453 }
454
Girish Kumarda415372020-04-21 15:37:13 +0000455 // Breakup into component and package name; consider default package name if it is blank (e.g. read-write-core#)
Girish Kumarb03dcee2020-04-14 11:48:15 +0000456 if len(val) > 1 {
457 if val[0] == defaultComponentName {
458 return nil, errors.New("global level doesn't support packageName")
459 }
Girish Kumarda415372020-04-21 15:37:13 +0000460
Girish Kumarb03dcee2020-04-14 11:48:15 +0000461 logConfig.ComponentName = val[0]
Girish Kumarda415372020-04-21 15:37:13 +0000462 logConfig.PackageName = val[1]
463 if logConfig.PackageName == "" {
464 logConfig.PackageName = defaultPackageName
465 }
Girish Kumarb03dcee2020-04-14 11:48:15 +0000466 } else {
467 logConfig.ComponentName = component
468 logConfig.PackageName = defaultPackageName
469 }
470 logLevelConfig = append(logLevelConfig, logConfig)
471 }
472 return logLevelConfig, nil
473}
474
475// This method set loglevel for components.
476// For example, using below command loglevel can be set for specific component with default packageName
477// voltctl loglevel set level <componentName>
478// For example, using below command loglevel can be set for specific component with specific packageName
479// voltctl loglevel set level <componentName#packageName>
480// For example, using below command loglevel can be set for more than one component for default package and other component for specific packageName
481// voltctl loglevel set level <componentName1#packageName> <componentName2>
482func (options *SetLogLevelOpts) Execute(args []string) error {
483 var (
484 logLevelConfig []model.LogLevel
485 err error
486 )
487 ProcessGlobalOptions()
488
489 log.SetAllLogLevel(log.FatalLevel)
490
491 if options.Args.Level != "" {
492 if _, err := log.StringToLogLevel(string(options.Args.Level)); err != nil {
493 return fmt.Errorf("Unknown log level '%s'. Allowed values are DEBUG, INFO, WARN, ERROR, FATAL", options.Args.Level)
494 }
495 }
496
497 logLevelConfig, err = processComponentListArgs(toStringArray(options.Args.Component))
498 if err != nil {
499 return fmt.Errorf(err.Error())
500 }
501
502 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), log.FatalLevel)
503 if err != nil {
504 return fmt.Errorf("Unable to create kvstore client %s", err)
505 }
506 defer client.Close()
507
508 // Already error checked during option processing
509 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
510 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
511
512 var output []LogLevelOutput
513
514 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
515 defer cancel()
516
Girish Kumarda415372020-04-21 15:37:13 +0000517 validComponents := getVolthaComponentNames()
518
Girish Kumarb03dcee2020-04-14 11:48:15 +0000519 for _, lConfig := range logLevelConfig {
520
521 logConfig := cm.InitComponentConfig(lConfig.ComponentName, config.ConfigTypeLogLevel)
Girish Kumarda415372020-04-21 15:37:13 +0000522 err := logConfig.Save(ctx, strings.ReplaceAll(lConfig.PackageName, "/", "#"), strings.ToUpper(string(options.Args.Level)))
523
Girish Kumarb03dcee2020-04-14 11:48:15 +0000524 if err != nil {
Girish Kumarda415372020-04-21 15:37:13 +0000525 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, PackageName: lConfig.PackageName, Status: "Failure", Error: err.Error()})
Girish Kumarb03dcee2020-04-14 11:48:15 +0000526 } else {
Girish Kumarda415372020-04-21 15:37:13 +0000527 var outmsg string
528 cvalid := false
529 pvalid := false
530
531 // Validate if component and package name being set are correct. Add a * against the invalid value
532
533 // For global level, only default package is valid
534 if lConfig.ComponentName == defaultComponentName {
535 if lConfig.PackageName != defaultPackageName {
536 lConfig.PackageName = "*" + lConfig.PackageName
537 outmsg = "Only default package is valid for global"
538 }
539 } else {
540
541 for _, cname := range validComponents {
542 if lConfig.ComponentName == cname {
543 cvalid = true
544 break
545 }
546 }
547
548 // If component is valid, fetch and validate entered package name
549 if cvalid {
550 if validPackages, err := getPackageNames(lConfig.ComponentName); err == nil {
551 for _, pname := range validPackages {
552 if lConfig.PackageName == pname {
553 pvalid = true
554 break
555 }
556 }
557 }
558
559 if !pvalid {
560 lConfig.PackageName = "*" + lConfig.PackageName
561 outmsg = "Entered Package Name is not valid"
562 }
563 } else {
564
565 lConfig.ComponentName = "*" + lConfig.ComponentName
566 outmsg = "Entered Component Name is not Currently active in Voltha"
567 }
568 }
569
570 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, PackageName: lConfig.PackageName, Status: "Success", Error: outmsg})
Girish Kumarb03dcee2020-04-14 11:48:15 +0000571 }
572
573 }
574
575 outputFormat := CharReplacer.Replace(options.Format)
576 if outputFormat == "" {
577 outputFormat = GetCommandOptionWithDefault("log-level-set", "format", DEFAULT_LOG_RESULT_FORMAT)
578 }
579 result := CommandResult{
580 Format: format.Format(outputFormat),
581 OutputAs: toOutputType(options.OutputAs),
582 NameLimit: options.NameLimit,
583 Data: output,
584 }
585
586 GenerateOutput(&result)
587 return nil
588}
589
590// This method list loglevel for components.
591// For example, using below command loglevel can be list for specific component
592// voltctl loglevel list <componentName>
593// For example, using below command loglevel can be list for all the components with all the packageName
594// voltctl loglevel list
595func (options *ListLogLevelsOpts) Execute(args []string) error {
596
597 var (
598 // Initialize to empty as opposed to nil so that -o json will
599 // display empty list and not null VOL-2742
600 data []model.LogLevel = []model.LogLevel{}
601 componentList []string
602 logLevelConfig map[string]string
603 err error
604 )
605 ProcessGlobalOptions()
606
607 log.SetAllLogLevel(log.FatalLevel)
608
609 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), log.FatalLevel)
610 if err != nil {
611 return fmt.Errorf("Unable to create kvstore client %s", err)
612 }
613 defer client.Close()
614
615 // Already error checked during option processing
616 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
617 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
618
619 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
620 defer cancel()
621
622 if len(options.Args.Component) == 0 {
623 componentList, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
624 if err != nil {
625 return fmt.Errorf("Unable to retrieve list of voltha components : %s ", err)
626 }
627 } else {
628 componentList = toStringArray(options.Args.Component)
629 }
630
Girish Kumarda415372020-04-21 15:37:13 +0000631 validComponents := getVolthaComponentNames()
632
Girish Kumarb03dcee2020-04-14 11:48:15 +0000633 for _, componentName := range componentList {
634 logConfig := cm.InitComponentConfig(componentName, config.ConfigTypeLogLevel)
635
636 logLevelConfig, err = logConfig.RetrieveAll(ctx)
637 if err != nil {
638 return fmt.Errorf("Unable to retrieve loglevel configuration for component %s : %s", componentName, err)
639 }
640
641 for packageName, level := range logLevelConfig {
642 logLevel := model.LogLevel{}
643 if packageName == "" {
644 continue
645 }
646
Girish Kumarda415372020-04-21 15:37:13 +0000647 cvalid := false
648 pvalid := false
649 outPackageName := strings.ReplaceAll(packageName, "#", "/")
650 outComponentName := componentName
651
652 // Validate retrieved component and package names before printing. Add a * against the invalid value
653 if componentName == defaultComponentName {
654 if packageName != defaultPackageName {
655 outPackageName = "*" + outPackageName
656 }
657 } else {
658 for _, cname := range validComponents {
659 if componentName == cname {
660 cvalid = true
661 break
662 }
663 }
664
665 // For valid component, fetch and verify package name as well
666 if cvalid {
667 if validPackages, err := getPackageNames(componentName); err == nil {
668 for _, pname := range validPackages {
669 if outPackageName == pname {
670 pvalid = true
671 break
672 }
673 }
674 }
675
676 if !pvalid {
677 outPackageName = "*" + outPackageName
678 }
679 } else {
680
681 outComponentName = "*" + componentName
682 }
683 }
684
685 logLevel.PopulateFrom(outComponentName, outPackageName, level)
Girish Kumarb03dcee2020-04-14 11:48:15 +0000686 data = append(data, logLevel)
687 }
688 }
689
690 outputFormat := CharReplacer.Replace(options.Format)
691 if outputFormat == "" {
692 outputFormat = GetCommandOptionWithDefault("log-level-list", "format", DEFAULT_LOG_LEVELS_FORMAT)
693 }
694 orderBy := options.OrderBy
695 if orderBy == "" {
696 orderBy = GetCommandOptionWithDefault("log-level-list", "order", "")
697 }
698
699 result := CommandResult{
700 Format: format.Format(outputFormat),
701 Filter: options.Filter,
702 OrderBy: orderBy,
703 OutputAs: toOutputType(options.OutputAs),
704 NameLimit: options.NameLimit,
705 Data: data,
706 }
707 GenerateOutput(&result)
708 return nil
709}
710
711// This method clear loglevel for components.
712// For example, using below command loglevel can be clear for specific component with default packageName
713// voltctl loglevel clear <componentName>
714// For example, using below command loglevel can be clear for specific component with specific packageName
715// voltctl loglevel clear <componentName#packageName>
716func (options *ClearLogLevelsOpts) Execute(args []string) error {
717
718 var (
719 logLevelConfig []model.LogLevel
720 err error
721 )
722 ProcessGlobalOptions()
723
724 log.SetAllLogLevel(log.FatalLevel)
725
726 logLevelConfig, err = processComponentListArgs(toStringArray(options.Args.Component))
727 if err != nil {
728 return fmt.Errorf("%s", err)
729 }
730
731 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), log.FatalLevel)
732 if err != nil {
733 return fmt.Errorf("Unable to create kvstore client %s", err)
734 }
735 defer client.Close()
736
737 // Already error checked during option processing
738 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
739 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
740
741 var output []LogLevelOutput
742
743 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
744 defer cancel()
745
746 for _, lConfig := range logLevelConfig {
747
748 if lConfig.ComponentName == defaultComponentName {
749 return fmt.Errorf("The global default loglevel cannot be cleared.")
750 }
751
752 logConfig := cm.InitComponentConfig(lConfig.ComponentName, config.ConfigTypeLogLevel)
753
Girish Kumarda415372020-04-21 15:37:13 +0000754 err := logConfig.Delete(ctx, strings.ReplaceAll(lConfig.PackageName, "/", "#"))
Girish Kumarb03dcee2020-04-14 11:48:15 +0000755 if err != nil {
Girish Kumarda415372020-04-21 15:37:13 +0000756 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, PackageName: lConfig.PackageName, Status: "Failure", Error: err.Error()})
Girish Kumarb03dcee2020-04-14 11:48:15 +0000757 } else {
Girish Kumarda415372020-04-21 15:37:13 +0000758 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, PackageName: lConfig.PackageName, Status: "Success"})
Girish Kumarb03dcee2020-04-14 11:48:15 +0000759 }
760 }
761
762 outputFormat := CharReplacer.Replace(options.Format)
763 if outputFormat == "" {
764 outputFormat = GetCommandOptionWithDefault("log-level-clear", "format", DEFAULT_LOG_RESULT_FORMAT)
765 }
766
767 result := CommandResult{
768 Format: format.Format(outputFormat),
769 OutputAs: toOutputType(options.OutputAs),
770 NameLimit: options.NameLimit,
771 Data: output,
772 }
773
774 GenerateOutput(&result)
775 return nil
776}
777
778// This method lists registered log packages for components.
779// For example, available log packages can be listed for specific component using below command
780// voltctl loglevel listpackage <componentName>
781// For example, available log packages can be listed for all the components using below command (omitting component name)
782// voltctl loglevel listpackage
783func (options *ListLogPackagesOpts) Execute(args []string) error {
784
785 var (
786 // Initialize to empty as opposed to nil so that -o json will
787 // display empty list and not null VOL-2742
788 data []model.LogLevel = []model.LogLevel{}
789 componentList []string
790 err error
791 )
792
793 ProcessGlobalOptions()
794
795 log.SetAllLogLevel(log.FatalLevel)
796
797 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), log.FatalLevel)
798 if err != nil {
799 return fmt.Errorf("Unable to create kvstore client %s", err)
800 }
801 defer client.Close()
802
803 // Already error checked during option processing
804 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
805 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
806
807 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
808 defer cancel()
809
810 if len(options.Args.Component) == 0 {
811 componentList, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
812 if err != nil {
813 return fmt.Errorf("Unable to retrieve list of voltha components : %s ", err)
814 }
815
816 // Include default global package as well when displaying packages for all components
817 logLevel := model.LogLevel{}
818 logLevel.PopulateFrom(defaultComponentName, defaultPackageName, "")
819 data = append(data, logLevel)
820 } else {
821 for _, name := range options.Args.Component {
822 componentList = append(componentList, string(name))
823 }
824 }
825
826 for _, componentName := range componentList {
827 componentMetadata := cm.InitComponentConfig(componentName, config.ConfigTypeMetadata)
828
829 value, err := componentMetadata.Retrieve(ctx, logPackagesListKey)
830 if err != nil || value == "" {
831 // Ignore any error in retrieval for log package list; some components may not store it
832 continue
833 }
834
835 var packageList []string
836 if err = json.Unmarshal([]byte(value), &packageList); err != nil {
837 continue
838 }
839
840 for _, packageName := range packageList {
841 logLevel := model.LogLevel{}
842 logLevel.PopulateFrom(componentName, packageName, "")
843 data = append(data, logLevel)
844 }
845 }
846
847 outputFormat := CharReplacer.Replace(options.Format)
848 if outputFormat == "" {
849 outputFormat = GetCommandOptionWithDefault("log-package-list", "format", DEFAULT_LOG_PACKAGES_FORMAT)
850 }
851 orderBy := options.OrderBy
852 if orderBy == "" {
853 orderBy = GetCommandOptionWithDefault("log-package-list", "order", "ComponentName,PackageName")
854 }
855
856 result := CommandResult{
857 Format: format.Format(outputFormat),
858 Filter: options.Filter,
859 OrderBy: orderBy,
860 OutputAs: toOutputType(options.OutputAs),
861 NameLimit: options.NameLimit,
862 Data: data,
863 }
864 GenerateOutput(&result)
865 return nil
866}