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