blob: 036abdca694485765c3e73d1e5c081766051099a [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
Girish Kumare48ec8a2020-08-18 12:28:51 +000042 logTracingStatusKey = "trace_publish"
Girish Kumarb03dcee2020-04-14 11:48:15 +000043)
44
45// Custom Option representing <component-name>#<package-name> format (package is optional)
46// This is used by 'log level set' commands
47type ComponentAndPackageName string
48
49// Custom Option representing currently configured log configuration in <component-name>#<package-name> format (package is optional)
50// This is used by 'log level clear' commands
51type ConfiguredComponentAndPackageName string
52
53// Custom Option representing component-name. This is used by 'log level list' and 'log package list' commands
54type ComponentName string
55
56// Custom Option representing Log Level (one of debug, info, warn, error, fatal)
57type LevelName string
58
59// LogLevelOutput represents the output structure for the loglevel
60type LogLevelOutput struct {
61 ComponentName string
Girish Kumarda415372020-04-21 15:37:13 +000062 PackageName string
Girish Kumarb03dcee2020-04-14 11:48:15 +000063 Status string
64 Error string
65}
66
Girish Kumare48ec8a2020-08-18 12:28:51 +000067// SetLogLevelOpts represents the supported CLI arguments for the log level set command
Girish Kumarb03dcee2020-04-14 11:48:15 +000068type SetLogLevelOpts struct {
69 OutputOptions
70 Args struct {
71 Level LevelName
72 Component []ComponentAndPackageName
73 } `positional-args:"yes" required:"yes"`
74}
75
Girish Kumare48ec8a2020-08-18 12:28:51 +000076// ListLogLevelOpts represents the supported CLI arguments for the log level list command
Girish Kumarb03dcee2020-04-14 11:48:15 +000077type ListLogLevelsOpts struct {
78 ListOutputOptions
79 Args struct {
80 Component []ComponentName
81 } `positional-args:"yes" required:"yes"`
82}
83
Girish Kumare48ec8a2020-08-18 12:28:51 +000084// ClearLogLevelOpts represents the supported CLI arguments for the log level clear command
Girish Kumarb03dcee2020-04-14 11:48:15 +000085type ClearLogLevelsOpts struct {
86 OutputOptions
87 Args struct {
88 Component []ConfiguredComponentAndPackageName
89 } `positional-args:"yes" required:"yes"`
90}
91
Girish Kumare48ec8a2020-08-18 12:28:51 +000092// ListLogLevelOpts represents the supported CLI arguments for the log level list command
Girish Kumarb03dcee2020-04-14 11:48:15 +000093type ListLogPackagesOpts struct {
94 ListOutputOptions
95 Args struct {
96 Component []ComponentName
97 } `positional-args:"yes" required:"yes"`
98}
99
Girish Kumare48ec8a2020-08-18 12:28:51 +0000100// EnableLogTracingOpts represents the supported CLI arguments for the log tracing enable command
101type EnableLogTracingOpts struct {
102 OutputOptions
103 Args struct {
104 Component []ComponentName
105 } `positional-args:"yes" required:"yes"`
106}
107
108// DisableLogTracingOpts represents the supported CLI arguments for the log tracing disable command
109type DisableLogTracingOpts struct {
110 OutputOptions
111 Args struct {
112 Component []ComponentName
113 } `positional-args:"yes" required:"yes"`
114}
115
116// ListLogTracingOpts represents the supported CLI arguments for the log tracing list command
117type ListLogTracingOpts struct {
118 ListOutputOptions
119 Args struct {
120 Component []ComponentName
121 } `positional-args:"yes" required:"yes"`
122}
123
Girish Kumarb03dcee2020-04-14 11:48:15 +0000124// LogPackageOpts represents the log package commands
125type LogPackageOpts struct {
126 ListLogPackages ListLogPackagesOpts `command:"list"`
127}
128
129// LogLevelOpts represents the log level commands
130type LogLevelOpts struct {
131 SetLogLevel SetLogLevelOpts `command:"set"`
132 ListLogLevels ListLogLevelsOpts `command:"list"`
133 ClearLogLevels ClearLogLevelsOpts `command:"clear"`
134}
135
Girish Kumare48ec8a2020-08-18 12:28:51 +0000136// LogTracingOpts represents the log tracing commands
137type LogTracingOpts struct {
138 EnableLogTracing EnableLogTracingOpts `command:"enable"`
139 DisableLogTracing DisableLogTracingOpts `command:"disable"`
140 ListLogTracing ListLogTracingOpts `command:"list"`
141}
142
Girish Kumarb03dcee2020-04-14 11:48:15 +0000143// LogOpts represents the log commands
144type LogOpts struct {
145 LogLevel LogLevelOpts `command:"level"`
146 LogPackage LogPackageOpts `command:"package"`
Girish Kumare48ec8a2020-08-18 12:28:51 +0000147 LogTracing LogTracingOpts `command:"tracing"`
Girish Kumarb03dcee2020-04-14 11:48:15 +0000148}
149
150var logOpts = LogOpts{}
151
152const (
Girish Kumare48ec8a2020-08-18 12:28:51 +0000153 DEFAULT_LOG_LEVELS_FORMAT = "table{{ .ComponentName }}\t{{.PackageName}}\t{{.Level}}"
154 DEFAULT_LOG_PACKAGES_FORMAT = "table{{ .ComponentName }}\t{{.PackageName}}"
155 DEFAULT_LOG_FEATURE_STATUS_FORMAT = "table{{ .ComponentName }}\t{{.Status}}"
156 DEFAULT_LOG_RESULT_FORMAT = "table{{ .ComponentName }}\t{{.PackageName}}\t{{.Status}}\t{{.Error}}"
157 DEFAULT_LOG_FEATURE_RESULT_FORMAT = "table{{ .ComponentName }}\t{{.Status}}\t{{.Error}}"
Girish Kumarb03dcee2020-04-14 11:48:15 +0000158)
159
160func toStringArray(arg interface{}) []string {
161 var list []string
162 if cnl, ok := arg.([]ComponentName); ok {
163 for _, cn := range cnl {
164 list = append(list, string(cn))
165 }
166 } else if cpnl, ok := arg.([]ComponentAndPackageName); ok {
167 for _, cpn := range cpnl {
168 list = append(list, string(cpn))
169 }
170 } else if ccpnl, ok := arg.([]ConfiguredComponentAndPackageName); ok {
171 for _, ccpn := range ccpnl {
172 list = append(list, string(ccpn))
173 }
174 }
175
176 return list
177}
178
179// RegisterLogCommands is used to register log and its sub-commands e.g. level, package etc
180func RegisterLogCommands(parent *flags.Parser) {
Girish Kumare48ec8a2020-08-18 12:28:51 +0000181 _, err := parent.AddCommand("log", "log configuration commands", "update/view log levels, correlation, tracing status and list packages of components", &logOpts)
Girish Kumarb03dcee2020-04-14 11:48:15 +0000182 if err != nil {
183 Error.Fatalf("Unable to register log commands with voltctl command parser: %s", err.Error())
184 }
185}
186
187// Common method to get list of VOLTHA components using k8s API. Used for validation and auto-complete
188// Just return a blank list in case of any error
189func getVolthaComponentNames() []string {
190 var componentList []string
191
192 // use the current context in kubeconfig
193 config, err := clientcmd.BuildConfigFromFlags("", GlobalOptions.K8sConfig)
194 if err != nil {
195 // Ignore any error
196 return componentList
197 }
198
199 // create the clientset
200 clientset, err := kubernetes.NewForConfig(config)
201 if err != nil {
202 return componentList
203 }
204
205 pods, err := clientset.CoreV1().Pods("").List(metav1.ListOptions{
206 LabelSelector: "app.kubernetes.io/part-of=voltha",
207 })
208 if err != nil {
209 return componentList
210 }
211
212 for _, pod := range pods.Items {
213 componentList = append(componentList, pod.ObjectMeta.Labels["app.kubernetes.io/name"])
214 }
215
216 return componentList
217}
218
Girish Kumare48ec8a2020-08-18 12:28:51 +0000219func constructConfigManager(ctx context.Context) (*config.ConfigManager, func(), error) {
220 client, err := kvstore.NewEtcdClient(ctx, GlobalConfig.KvStore, GlobalConfig.KvStoreConfig.Timeout, log.FatalLevel)
221 if err != nil {
222 return nil, nil, fmt.Errorf("Unable to create kvstore client %s", err)
223 }
224
225 // Already error checked during option processing
226 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
227 cm := config.NewConfigManager(ctx, client, supportedKvStoreType, net.JoinHostPort(host, strconv.Itoa(port)), GlobalConfig.KvStoreConfig.Timeout)
228 return cm, func() { client.Close(ctx) }, nil
229}
230
Girish Kumarda415372020-04-21 15:37:13 +0000231// Method to get list of allowed Package Names for a given component. This list
232// is saved into etcd kvstore by each active component at startup as a json array
Girish Kumarb03dcee2020-04-14 11:48:15 +0000233func getPackageNames(componentName string) ([]string, error) {
234 list := []string{defaultPackageName}
235
236 ProcessGlobalOptions()
237
238 log.SetAllLogLevel(log.FatalLevel)
239
Girish Kumarb03dcee2020-04-14 11:48:15 +0000240 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
241 defer cancel()
242
Girish Kumare48ec8a2020-08-18 12:28:51 +0000243 cm, cleanupFunc, err := constructConfigManager(ctx)
244 if err != nil {
245 return nil, fmt.Errorf("Error while constructing ConfigManager instance : %s", err)
246 }
247 defer cleanupFunc()
248
Girish Kumarb03dcee2020-04-14 11:48:15 +0000249 componentMetadata := cm.InitComponentConfig(componentName, config.ConfigTypeMetadata)
250
251 value, err := componentMetadata.Retrieve(ctx, logPackagesListKey)
252 if err != nil || value == "" {
253 return list, nil
254 }
255
256 var packageList []string
257 if err = json.Unmarshal([]byte(value), &packageList); err != nil {
258 return list, nil
259 }
260
261 list = append(list, packageList...)
262
263 return list, nil
264}
265
266func (ln *LevelName) Complete(match string) []flags.Completion {
267 levels := []string{"debug", "info", "warn", "error", "fatal"}
268
269 var list []flags.Completion
270 for _, name := range levels {
271 if strings.HasPrefix(name, strings.ToLower(match)) {
272 list = append(list, flags.Completion{Item: name})
273 }
274 }
275
276 return list
277}
278
279func (cpn *ComponentAndPackageName) Complete(match string) []flags.Completion {
280
281 componentNames := getVolthaComponentNames()
282
283 // Return nil if no component names could be fetched
284 if len(componentNames) == 0 {
285 return nil
286 }
287
288 // Check to see if #was specified, and if so, we know we have
289 // to split component name and package
290 parts := strings.SplitN(match, "#", 2)
291
292 var list []flags.Completion
293 for _, name := range componentNames {
294 if strings.HasPrefix(name, parts[0]) {
295 list = append(list, flags.Completion{Item: name})
296 }
297 }
298
299 // If the possible completions > 1 then we have to stop here
300 // as we can't suggest packages
301 if len(parts) == 1 || len(list) > 1 {
302 return list
303 }
304
305 // Ok, we have a valid, unambiguous component name and there
306 // is a package separator, so lets try to expand the package
307 // and in this case we will replace the list we have so
308 // far with the new list
309 cname := list[0].Item
310 base := []flags.Completion{{Item: fmt.Sprintf("%s#%s", cname, parts[1])}}
311 packages, err := getPackageNames(cname)
312 if err != nil || len(packages) == 0 {
313 return base
314 }
315
316 list = []flags.Completion{}
317 for _, pname := range packages {
318 if strings.HasPrefix(pname, parts[1]) {
319 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s", cname, pname)})
320 }
321 }
322
323 // if package part is present and still no match found based on prefix, user may be using
324 // short-hand notation for package name (last element of package path string e.g. kafka).
325 // Attempt prefix match against last element of package path (after the last / character)
326 if len(list) == 0 && len(parts[1]) >= 3 {
327 var mplist []string
328 for _, pname := range packages {
329 pnameparts := strings.Split(pname, "/")
330 if strings.HasPrefix(pnameparts[len(pnameparts)-1], parts[1]) {
331 mplist = append(mplist, pname)
332 }
333 }
334
335 // add to completion list if only a single match is found
336 if len(mplist) == 1 {
337 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s", cname, mplist[0])})
338 }
339 }
340
341 // If the component name was expanded but package name match was not found, list will still be empty
342 // We should return entry with just component name auto-completed and package name unchanged.
343 if len(list) == 0 && cname != parts[0] {
344 // Returning 2 entries with <completed-component-name>#<package-part> as prefix
345 // Just 1 entry will auto-complete the argument
346 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s1", cname, parts[1])})
347 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s2", cname, parts[1])})
348 }
349
350 return list
351}
352
353func (ccpn *ConfiguredComponentAndPackageName) Complete(match string) []flags.Completion {
354
355 var list []flags.Completion
356
357 ProcessGlobalOptions()
358
359 log.SetAllLogLevel(log.FatalLevel)
360
Girish Kumare48ec8a2020-08-18 12:28:51 +0000361 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
362 defer cancel()
363
364 cm, cleanupFunc, err := constructConfigManager(ctx)
Girish Kumarb03dcee2020-04-14 11:48:15 +0000365 if err != nil {
366 return list
367 }
Girish Kumare48ec8a2020-08-18 12:28:51 +0000368 defer cleanupFunc()
Girish Kumarb03dcee2020-04-14 11:48:15 +0000369
370 var componentNames []string
371 componentNames, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
372
373 // Return nil if no component names could be fetched
374 if err != nil || len(componentNames) == 0 {
375 return nil
376 }
377
378 // Check to see if #was specified, and if so, we know we have
379 // to split component name and package
380 parts := strings.SplitN(match, "#", 2)
381
382 for _, name := range componentNames {
383 if strings.HasPrefix(name, parts[0]) {
384 list = append(list, flags.Completion{Item: name})
385
386 // Handle scenario when one component is exact substring of other e.g. read-write-cor
387 // is substring of read-write-core (last e missing). Such a wrong component name
388 // can get configured during log level set operation
389 // In case of exact match of component name, use it if package part is present
390 if name == parts[0] && len(parts) == 2 {
391 list = []flags.Completion{{Item: name}}
392 break
393 }
394 }
395 }
396
397 // If the possible completions > 1 then we have to stop here
398 // as we can't suggest packages
399 if len(parts) == 1 || len(list) > 1 {
400 return list
401 }
402
403 // Ok, we have a valid, unambiguous component name and there
404 // is a package separator, so lets try to expand the package
405 // and in this case we will replace the list we have so
406 // far with the new list
407 cname := list[0].Item
408 base := []flags.Completion{{Item: fmt.Sprintf("%s#%s", cname, parts[1])}}
409
410 // Get list of packages configured for matching component name
411 logConfig := cm.InitComponentConfig(cname, config.ConfigTypeLogLevel)
412 logLevels, err1 := logConfig.RetrieveAll(ctx)
413 if err1 != nil || len(logLevels) == 0 {
414 return base
415 }
416
417 packages := make([]string, len(logLevels))
418 list = []flags.Completion{}
419 for pname := range logLevels {
420 pname = strings.ReplaceAll(pname, "#", "/")
421 packages = append(packages, pname)
422 if strings.HasPrefix(pname, parts[1]) {
423 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s", cname, pname)})
424 }
425 }
426
427 // if package part is present and still no match found based on prefix, user may be using
428 // short-hand notation for package name (last element of package path string e.g. kafka).
429 // Attempt prefix match against last element of package path (after the last / character)
430 if len(list) == 0 && len(parts[1]) >= 3 {
431 var mplist []string
432 for _, pname := range packages {
433 pnameparts := strings.Split(pname, "/")
434 if strings.HasPrefix(pnameparts[len(pnameparts)-1], parts[1]) {
435 mplist = append(mplist, pname)
436 }
437 }
438
439 // add to completion list if only a single match is found
440 if len(mplist) == 1 {
441 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s", cname, mplist[0])})
442 }
443 }
444
445 // If the component name was expanded but package name match was not found, list will still be empty
446 // We should return entry with just component name auto-completed and package name unchanged.
447 if len(list) == 0 && cname != parts[0] {
448 // Returning 2 entries with <completed-component-name>#<package-part> as prefix
449 // Just 1 entry will auto-complete the argument
450 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s1", cname, parts[1])})
451 list = append(list, flags.Completion{Item: fmt.Sprintf("%s#%s2", cname, parts[1])})
452 }
453
454 return list
455}
456
457func (cn *ComponentName) Complete(match string) []flags.Completion {
458
459 componentNames := getVolthaComponentNames()
460
461 // Return nil if no component names could be fetched
462 if len(componentNames) == 0 {
463 return nil
464 }
465
466 var list []flags.Completion
467 for _, name := range componentNames {
468 if strings.HasPrefix(name, match) {
469 list = append(list, flags.Completion{Item: name})
470 }
471 }
472
473 return list
474}
475
476// Return nil if no component names could be fetched
477// processComponentListArgs stores the component name and package names given in command arguments to LogLevel
478// It checks the given argument has # key or not, if # is present then split the argument for # then stores first part as component name
479// and second part as package name
480func processComponentListArgs(Components []string) ([]model.LogLevel, error) {
481
482 var logLevelConfig []model.LogLevel
483
484 if len(Components) == 0 {
485 Components = append(Components, defaultComponentName)
486 }
487
488 for _, component := range Components {
489 logConfig := model.LogLevel{}
490 val := strings.SplitN(component, "#", 2)
491
492 if strings.Contains(val[0], "/") {
493 return nil, errors.New("the component name '" + val[0] + "' contains an invalid character '/'")
494 }
495
Girish Kumarda415372020-04-21 15:37:13 +0000496 // 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 +0000497 if len(val) > 1 {
498 if val[0] == defaultComponentName {
499 return nil, errors.New("global level doesn't support packageName")
500 }
Girish Kumarda415372020-04-21 15:37:13 +0000501
Girish Kumarb03dcee2020-04-14 11:48:15 +0000502 logConfig.ComponentName = val[0]
Girish Kumarda415372020-04-21 15:37:13 +0000503 logConfig.PackageName = val[1]
504 if logConfig.PackageName == "" {
505 logConfig.PackageName = defaultPackageName
506 }
Girish Kumarb03dcee2020-04-14 11:48:15 +0000507 } else {
508 logConfig.ComponentName = component
509 logConfig.PackageName = defaultPackageName
510 }
511 logLevelConfig = append(logLevelConfig, logConfig)
512 }
513 return logLevelConfig, nil
514}
515
516// This method set loglevel for components.
517// For example, using below command loglevel can be set for specific component with default packageName
518// voltctl loglevel set level <componentName>
519// For example, using below command loglevel can be set for specific component with specific packageName
520// voltctl loglevel set level <componentName#packageName>
521// For example, using below command loglevel can be set for more than one component for default package and other component for specific packageName
522// voltctl loglevel set level <componentName1#packageName> <componentName2>
523func (options *SetLogLevelOpts) Execute(args []string) error {
524 var (
525 logLevelConfig []model.LogLevel
526 err error
527 )
528 ProcessGlobalOptions()
529
530 log.SetAllLogLevel(log.FatalLevel)
531
532 if options.Args.Level != "" {
533 if _, err := log.StringToLogLevel(string(options.Args.Level)); err != nil {
534 return fmt.Errorf("Unknown log level '%s'. Allowed values are DEBUG, INFO, WARN, ERROR, FATAL", options.Args.Level)
535 }
536 }
537
538 logLevelConfig, err = processComponentListArgs(toStringArray(options.Args.Component))
539 if err != nil {
540 return fmt.Errorf(err.Error())
541 }
542
Girish Kumarb03dcee2020-04-14 11:48:15 +0000543 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
544 defer cancel()
545
Girish Kumare48ec8a2020-08-18 12:28:51 +0000546 cm, cleanupFunc, err := constructConfigManager(ctx)
547 if err != nil {
548 return fmt.Errorf("Error while constructing ConfigManager instance : %s", err)
549 }
550 defer cleanupFunc()
551
552 var output []LogLevelOutput
553
Girish Kumarda415372020-04-21 15:37:13 +0000554 validComponents := getVolthaComponentNames()
555
Girish Kumarb03dcee2020-04-14 11:48:15 +0000556 for _, lConfig := range logLevelConfig {
557
558 logConfig := cm.InitComponentConfig(lConfig.ComponentName, config.ConfigTypeLogLevel)
Girish Kumarda415372020-04-21 15:37:13 +0000559 err := logConfig.Save(ctx, strings.ReplaceAll(lConfig.PackageName, "/", "#"), strings.ToUpper(string(options.Args.Level)))
560
Girish Kumarb03dcee2020-04-14 11:48:15 +0000561 if err != nil {
Girish Kumarda415372020-04-21 15:37:13 +0000562 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, PackageName: lConfig.PackageName, Status: "Failure", Error: err.Error()})
Girish Kumarb03dcee2020-04-14 11:48:15 +0000563 } else {
Girish Kumarda415372020-04-21 15:37:13 +0000564 var outmsg string
565 cvalid := false
566 pvalid := false
567
568 // Validate if component and package name being set are correct. Add a * against the invalid value
569
570 // For global level, only default package is valid
571 if lConfig.ComponentName == defaultComponentName {
572 if lConfig.PackageName != defaultPackageName {
573 lConfig.PackageName = "*" + lConfig.PackageName
574 outmsg = "Only default package is valid for global"
575 }
576 } else {
577
578 for _, cname := range validComponents {
579 if lConfig.ComponentName == cname {
580 cvalid = true
581 break
582 }
583 }
584
585 // If component is valid, fetch and validate entered package name
586 if cvalid {
587 if validPackages, err := getPackageNames(lConfig.ComponentName); err == nil {
588 for _, pname := range validPackages {
589 if lConfig.PackageName == pname {
590 pvalid = true
591 break
592 }
593 }
594 }
595
596 if !pvalid {
597 lConfig.PackageName = "*" + lConfig.PackageName
598 outmsg = "Entered Package Name is not valid"
599 }
600 } else {
601
602 lConfig.ComponentName = "*" + lConfig.ComponentName
603 outmsg = "Entered Component Name is not Currently active in Voltha"
604 }
605 }
606
607 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, PackageName: lConfig.PackageName, Status: "Success", Error: outmsg})
Girish Kumarb03dcee2020-04-14 11:48:15 +0000608 }
609
610 }
611
612 outputFormat := CharReplacer.Replace(options.Format)
613 if outputFormat == "" {
614 outputFormat = GetCommandOptionWithDefault("log-level-set", "format", DEFAULT_LOG_RESULT_FORMAT)
615 }
616 result := CommandResult{
617 Format: format.Format(outputFormat),
618 OutputAs: toOutputType(options.OutputAs),
619 NameLimit: options.NameLimit,
620 Data: output,
621 }
622
623 GenerateOutput(&result)
624 return nil
625}
626
627// This method list loglevel for components.
628// For example, using below command loglevel can be list for specific component
629// voltctl loglevel list <componentName>
630// For example, using below command loglevel can be list for all the components with all the packageName
631// voltctl loglevel list
632func (options *ListLogLevelsOpts) Execute(args []string) error {
633
634 var (
635 // Initialize to empty as opposed to nil so that -o json will
636 // display empty list and not null VOL-2742
637 data []model.LogLevel = []model.LogLevel{}
638 componentList []string
639 logLevelConfig map[string]string
640 err error
641 )
642 ProcessGlobalOptions()
643
644 log.SetAllLogLevel(log.FatalLevel)
645
Girish Kumarb03dcee2020-04-14 11:48:15 +0000646 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
647 defer cancel()
648
Girish Kumare48ec8a2020-08-18 12:28:51 +0000649 cm, cleanupFunc, err := constructConfigManager(ctx)
650 if err != nil {
651 return fmt.Errorf("Error while constructing ConfigManager instance : %s", err)
652 }
653 defer cleanupFunc()
654
Girish Kumarb03dcee2020-04-14 11:48:15 +0000655 if len(options.Args.Component) == 0 {
656 componentList, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
657 if err != nil {
Girish Kumare48ec8a2020-08-18 12:28:51 +0000658 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
Matteo Scandolo11b74902020-06-09 15:27:32 -0700659 return fmt.Errorf("Unable to retrieve list of voltha components : %s \nIs ETCD available at %s:%d?", err, host, port)
Girish Kumarb03dcee2020-04-14 11:48:15 +0000660 }
661 } else {
662 componentList = toStringArray(options.Args.Component)
663 }
664
Girish Kumarda415372020-04-21 15:37:13 +0000665 validComponents := getVolthaComponentNames()
666
Girish Kumarb03dcee2020-04-14 11:48:15 +0000667 for _, componentName := range componentList {
668 logConfig := cm.InitComponentConfig(componentName, config.ConfigTypeLogLevel)
669
670 logLevelConfig, err = logConfig.RetrieveAll(ctx)
671 if err != nil {
672 return fmt.Errorf("Unable to retrieve loglevel configuration for component %s : %s", componentName, err)
673 }
674
675 for packageName, level := range logLevelConfig {
676 logLevel := model.LogLevel{}
677 if packageName == "" {
678 continue
679 }
680
Girish Kumarda415372020-04-21 15:37:13 +0000681 cvalid := false
682 pvalid := false
683 outPackageName := strings.ReplaceAll(packageName, "#", "/")
684 outComponentName := componentName
685
686 // Validate retrieved component and package names before printing. Add a * against the invalid value
687 if componentName == defaultComponentName {
688 if packageName != defaultPackageName {
689 outPackageName = "*" + outPackageName
690 }
691 } else {
692 for _, cname := range validComponents {
693 if componentName == cname {
694 cvalid = true
695 break
696 }
697 }
698
699 // For valid component, fetch and verify package name as well
700 if cvalid {
701 if validPackages, err := getPackageNames(componentName); err == nil {
702 for _, pname := range validPackages {
703 if outPackageName == pname {
704 pvalid = true
705 break
706 }
707 }
708 }
709
710 if !pvalid {
711 outPackageName = "*" + outPackageName
712 }
713 } else {
714
715 outComponentName = "*" + componentName
716 }
717 }
718
719 logLevel.PopulateFrom(outComponentName, outPackageName, level)
Girish Kumarb03dcee2020-04-14 11:48:15 +0000720 data = append(data, logLevel)
721 }
722 }
723
724 outputFormat := CharReplacer.Replace(options.Format)
725 if outputFormat == "" {
726 outputFormat = GetCommandOptionWithDefault("log-level-list", "format", DEFAULT_LOG_LEVELS_FORMAT)
727 }
728 orderBy := options.OrderBy
729 if orderBy == "" {
730 orderBy = GetCommandOptionWithDefault("log-level-list", "order", "")
731 }
732
733 result := CommandResult{
734 Format: format.Format(outputFormat),
735 Filter: options.Filter,
736 OrderBy: orderBy,
737 OutputAs: toOutputType(options.OutputAs),
738 NameLimit: options.NameLimit,
739 Data: data,
740 }
741 GenerateOutput(&result)
742 return nil
743}
744
745// This method clear loglevel for components.
746// For example, using below command loglevel can be clear for specific component with default packageName
747// voltctl loglevel clear <componentName>
748// For example, using below command loglevel can be clear for specific component with specific packageName
749// voltctl loglevel clear <componentName#packageName>
750func (options *ClearLogLevelsOpts) Execute(args []string) error {
751
752 var (
753 logLevelConfig []model.LogLevel
754 err error
755 )
756 ProcessGlobalOptions()
757
758 log.SetAllLogLevel(log.FatalLevel)
759
760 logLevelConfig, err = processComponentListArgs(toStringArray(options.Args.Component))
761 if err != nil {
762 return fmt.Errorf("%s", err)
763 }
764
Girish Kumarb03dcee2020-04-14 11:48:15 +0000765 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
766 defer cancel()
767
Girish Kumare48ec8a2020-08-18 12:28:51 +0000768 cm, cleanupFunc, err := constructConfigManager(ctx)
769 if err != nil {
770 return fmt.Errorf("Error while constructing ConfigManager instance : %s", err)
771 }
772 defer cleanupFunc()
773
774 var output []LogLevelOutput
775
Girish Kumarb03dcee2020-04-14 11:48:15 +0000776 for _, lConfig := range logLevelConfig {
777
778 if lConfig.ComponentName == defaultComponentName {
779 return fmt.Errorf("The global default loglevel cannot be cleared.")
780 }
781
782 logConfig := cm.InitComponentConfig(lConfig.ComponentName, config.ConfigTypeLogLevel)
783
Girish Kumarda415372020-04-21 15:37:13 +0000784 err := logConfig.Delete(ctx, strings.ReplaceAll(lConfig.PackageName, "/", "#"))
Girish Kumarb03dcee2020-04-14 11:48:15 +0000785 if err != nil {
Girish Kumarda415372020-04-21 15:37:13 +0000786 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, PackageName: lConfig.PackageName, Status: "Failure", Error: err.Error()})
Girish Kumarb03dcee2020-04-14 11:48:15 +0000787 } else {
Girish Kumarda415372020-04-21 15:37:13 +0000788 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, PackageName: lConfig.PackageName, Status: "Success"})
Girish Kumarb03dcee2020-04-14 11:48:15 +0000789 }
790 }
791
792 outputFormat := CharReplacer.Replace(options.Format)
793 if outputFormat == "" {
794 outputFormat = GetCommandOptionWithDefault("log-level-clear", "format", DEFAULT_LOG_RESULT_FORMAT)
795 }
796
797 result := CommandResult{
798 Format: format.Format(outputFormat),
799 OutputAs: toOutputType(options.OutputAs),
800 NameLimit: options.NameLimit,
801 Data: output,
802 }
803
804 GenerateOutput(&result)
805 return nil
806}
807
808// This method lists registered log packages for components.
809// For example, available log packages can be listed for specific component using below command
810// voltctl loglevel listpackage <componentName>
811// For example, available log packages can be listed for all the components using below command (omitting component name)
812// voltctl loglevel listpackage
813func (options *ListLogPackagesOpts) Execute(args []string) error {
814
815 var (
816 // Initialize to empty as opposed to nil so that -o json will
817 // display empty list and not null VOL-2742
818 data []model.LogLevel = []model.LogLevel{}
819 componentList []string
820 err error
821 )
822
823 ProcessGlobalOptions()
824
825 log.SetAllLogLevel(log.FatalLevel)
826
Girish Kumarb03dcee2020-04-14 11:48:15 +0000827 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
828 defer cancel()
829
Girish Kumare48ec8a2020-08-18 12:28:51 +0000830 cm, cleanupFunc, err := constructConfigManager(ctx)
831 if err != nil {
832 return fmt.Errorf("Error while constructing ConfigManager instance : %s", err)
833 }
834 defer cleanupFunc()
835
Girish Kumarb03dcee2020-04-14 11:48:15 +0000836 if len(options.Args.Component) == 0 {
837 componentList, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
838 if err != nil {
839 return fmt.Errorf("Unable to retrieve list of voltha components : %s ", err)
840 }
841
842 // Include default global package as well when displaying packages for all components
843 logLevel := model.LogLevel{}
844 logLevel.PopulateFrom(defaultComponentName, defaultPackageName, "")
845 data = append(data, logLevel)
846 } else {
847 for _, name := range options.Args.Component {
848 componentList = append(componentList, string(name))
849 }
850 }
851
852 for _, componentName := range componentList {
853 componentMetadata := cm.InitComponentConfig(componentName, config.ConfigTypeMetadata)
854
855 value, err := componentMetadata.Retrieve(ctx, logPackagesListKey)
856 if err != nil || value == "" {
857 // Ignore any error in retrieval for log package list; some components may not store it
858 continue
859 }
860
861 var packageList []string
862 if err = json.Unmarshal([]byte(value), &packageList); err != nil {
863 continue
864 }
865
866 for _, packageName := range packageList {
867 logLevel := model.LogLevel{}
868 logLevel.PopulateFrom(componentName, packageName, "")
869 data = append(data, logLevel)
870 }
871 }
872
873 outputFormat := CharReplacer.Replace(options.Format)
874 if outputFormat == "" {
875 outputFormat = GetCommandOptionWithDefault("log-package-list", "format", DEFAULT_LOG_PACKAGES_FORMAT)
876 }
877 orderBy := options.OrderBy
878 if orderBy == "" {
879 orderBy = GetCommandOptionWithDefault("log-package-list", "order", "ComponentName,PackageName")
880 }
881
882 result := CommandResult{
883 Format: format.Format(outputFormat),
884 Filter: options.Filter,
885 OrderBy: orderBy,
886 OutputAs: toOutputType(options.OutputAs),
887 NameLimit: options.NameLimit,
888 Data: data,
889 }
890 GenerateOutput(&result)
891 return nil
892}
Girish Kumare48ec8a2020-08-18 12:28:51 +0000893
894// This method enables log trace publishing for components.
895// For example, using below command, trace publishing can be enabled for specific component
896// voltctl log tracing enable <componentName>
897// Omitting the component name will enable trace publishing for all the components, as shown in below command.
898// voltctl log tracing enable
899func (options *EnableLogTracingOpts) Execute(args []string) error {
900
901 var (
902 componentNames []string
903 err error
904 )
905
906 ProcessGlobalOptions()
907
908 log.SetAllLogLevel(log.FatalLevel)
909
910 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
911 defer cancel()
912
913 cm, cleanupFunc, err := constructConfigManager(ctx)
914 if err != nil {
915 return fmt.Errorf("Error while constructing ConfigManager instance : %s", err)
916 }
917 defer cleanupFunc()
918
919 var output []LogLevelOutput
920
921 if len(options.Args.Component) == 0 {
922 // Apply to all components if no specific component has been indicated
923 componentNames, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogFeatures)
924 if err != nil {
925 return fmt.Errorf("Unable to retrieve list of voltha components : %s ", err)
926 }
927
928 } else {
929 for _, name := range options.Args.Component {
930 componentNames = append(componentNames, string(name))
931 }
932 }
933
934 validComponents := getVolthaComponentNames()
935
936 for _, component := range componentNames {
937
938 config := cm.InitComponentConfig(component, config.ConfigTypeLogFeatures)
939
940 err := config.Save(ctx, logTracingStatusKey, "ENABLED")
941 if err != nil {
942 output = append(output, LogLevelOutput{ComponentName: component, Status: "Failure", Error: err.Error()})
943 } else {
944 outmsg := ""
945 cvalid := false
946 for _, cname := range validComponents {
947 if component == cname {
948 cvalid = true
949 break
950 }
951 }
952
953 // For invalid component, add * against its name to indicate possible mis-configuration
954 if !cvalid {
955 component = "*" + component
956 outmsg = "Entered Component Name is not Currently active in Voltha"
957 }
958
959 output = append(output, LogLevelOutput{ComponentName: component, Status: "Success", Error: outmsg})
960 }
961 }
962
963 outputFormat := CharReplacer.Replace(options.Format)
964 if outputFormat == "" {
965 outputFormat = GetCommandOptionWithDefault("log-tracing-enable", "format", DEFAULT_LOG_FEATURE_RESULT_FORMAT)
966 }
967
968 result := CommandResult{
969 Format: format.Format(outputFormat),
970 OutputAs: toOutputType(options.OutputAs),
971 NameLimit: options.NameLimit,
972 Data: output,
973 }
974
975 GenerateOutput(&result)
976 return nil
977}
978
979// This method disables log trace publishing for components.
980// For example, using below command, trace publishing can be disabled for specific component
981// voltctl log tracing disable <componentName>
982// Omitting the component name will disable trace publishing for all the components, as shown in below command.
983// voltctl log tracing disable
984func (options *DisableLogTracingOpts) Execute(args []string) error {
985
986 var (
987 componentNames []string
988 err error
989 )
990
991 ProcessGlobalOptions()
992
993 log.SetAllLogLevel(log.FatalLevel)
994
995 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
996 defer cancel()
997
998 cm, cleanupFunc, err := constructConfigManager(ctx)
999 if err != nil {
1000 return fmt.Errorf("Error while constructing ConfigManager instance : %s", err)
1001 }
1002 defer cleanupFunc()
1003
1004 var output []LogLevelOutput
1005
1006 if len(options.Args.Component) == 0 {
1007 // Apply to all components if no specific component has been indicated
1008 componentNames, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogFeatures)
1009 if err != nil {
1010 return fmt.Errorf("Unable to retrieve list of voltha components : %s ", err)
1011 }
1012
1013 } else {
1014 for _, name := range options.Args.Component {
1015 componentNames = append(componentNames, string(name))
1016 }
1017 }
1018
1019 validComponents := getVolthaComponentNames()
1020
1021 for _, component := range componentNames {
1022
1023 config := cm.InitComponentConfig(component, config.ConfigTypeLogFeatures)
1024
1025 err := config.Save(ctx, logTracingStatusKey, "DISABLED")
1026
1027 if err != nil {
1028 output = append(output, LogLevelOutput{ComponentName: component, Status: "Failure", Error: err.Error()})
1029 } else {
1030 outmsg := ""
1031 cvalid := false
1032 for _, cname := range validComponents {
1033 if component == cname {
1034 cvalid = true
1035 break
1036 }
1037 }
1038
1039 // For invalid component, add * against its name to indicate possible mis-configuration
1040 if !cvalid {
1041 component = "*" + component
1042 outmsg = "Entered Component Name is not Currently active in Voltha"
1043 }
1044
1045 output = append(output, LogLevelOutput{ComponentName: component, Status: "Success", Error: outmsg})
1046 }
1047 }
1048
1049 outputFormat := CharReplacer.Replace(options.Format)
1050 if outputFormat == "" {
1051 outputFormat = GetCommandOptionWithDefault("log-tracing-disable", "format", DEFAULT_LOG_FEATURE_RESULT_FORMAT)
1052 }
1053
1054 result := CommandResult{
1055 Format: format.Format(outputFormat),
1056 OutputAs: toOutputType(options.OutputAs),
1057 NameLimit: options.NameLimit,
1058 Data: output,
1059 }
1060
1061 GenerateOutput(&result)
1062 return nil
1063}
1064
1065// This method lists current status of log trace publishing for components.
1066// For example, using below command, trace publishing can be queried for specific component
1067// voltctl log tracing list <componentName>
1068// Omitting the component name will list trace publishing for all the components, as shown in below command.
1069// voltctl log tracing list
1070func (options *ListLogTracingOpts) Execute(args []string) error {
1071
1072 var (
1073 data []model.LogFeature = []model.LogFeature{}
1074 componentNames []string
1075 err error
1076 )
1077
1078 ProcessGlobalOptions()
1079
1080 log.SetAllLogLevel(log.FatalLevel)
1081
1082 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
1083 defer cancel()
1084
1085 cm, cleanupFunc, err := constructConfigManager(ctx)
1086 if err != nil {
1087 return fmt.Errorf("Error while constructing ConfigManager instance : %s", err)
1088 }
1089 defer cleanupFunc()
1090
1091 if len(options.Args.Component) == 0 {
1092 // Apply to all components if no specific component has been indicated
1093 componentNames, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogFeatures)
1094 if err != nil {
1095 return fmt.Errorf("Unable to retrieve list of voltha components : %s ", err)
1096 }
1097
1098 } else {
1099 for _, name := range options.Args.Component {
1100 componentNames = append(componentNames, string(name))
1101 }
1102 }
1103
1104 validComponents := getVolthaComponentNames()
1105
1106 for _, component := range componentNames {
1107
1108 config := cm.InitComponentConfig(component, config.ConfigTypeLogFeatures)
1109
1110 value, err := config.Retrieve(ctx, logTracingStatusKey)
1111 if err != nil || value == "" {
1112 // Ignore any error in retrieval; move to next component
1113 continue
1114 }
1115
1116 cvalid := false
1117 for _, cname := range validComponents {
1118 if component == cname {
1119 cvalid = true
1120 break
1121 }
1122 }
1123
1124 // For invalid component, add * against its name to indicate possible mis-configuration
1125 if !cvalid {
1126 component = "*" + component
1127 }
1128
1129 logTracingStatus := model.LogFeature{}
1130 logTracingStatus.PopulateFrom(component, value)
1131 data = append(data, logTracingStatus)
1132 }
1133
1134 outputFormat := CharReplacer.Replace(options.Format)
1135 if outputFormat == "" {
1136 outputFormat = GetCommandOptionWithDefault("log-tracing-list", "format", DEFAULT_LOG_FEATURE_STATUS_FORMAT)
1137 }
1138 orderBy := options.OrderBy
1139 if orderBy == "" {
1140 orderBy = GetCommandOptionWithDefault("log-tracing-list", "order", "ComponentName,Status")
1141 }
1142
1143 result := CommandResult{
1144 Format: format.Format(outputFormat),
1145 Filter: options.Filter,
1146 OrderBy: orderBy,
1147 OutputAs: toOutputType(options.OutputAs),
1148 NameLimit: options.NameLimit,
1149 Data: data,
1150 }
1151 GenerateOutput(&result)
1152 return nil
1153}