blob: 78876229f61f58ab1ce88867d9aa0061fb883aa3 [file] [log] [blame]
Scott Bakerd69e4222019-08-21 16:46:05 -07001/*
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"
divyadesai19009132020-03-04 12:58:08 +000020 "errors"
Scott Bakerd69e4222019-08-21 16:46:05 -070021 "fmt"
Scott Bakerd69e4222019-08-21 16:46:05 -070022 flags "github.com/jessevdk/go-flags"
Scott Bakerd69e4222019-08-21 16:46:05 -070023 "github.com/opencord/voltctl/pkg/format"
24 "github.com/opencord/voltctl/pkg/model"
divyadesai19009132020-03-04 12:58:08 +000025 "github.com/opencord/voltha-lib-go/v3/pkg/config"
26 "github.com/opencord/voltha-lib-go/v3/pkg/db/kvstore"
27 "github.com/opencord/voltha-lib-go/v3/pkg/log"
28 "io/ioutil"
29 "os"
Scott Bakerd69e4222019-08-21 16:46:05 -070030 "strings"
31)
32
divyadesai19009132020-03-04 12:58:08 +000033const (
34 defaultComponentName = "global"
35 defaultPackageName = "default"
36)
37
38// LogLevelOutput represents the output structure for the loglevel
39type LogLevelOutput struct {
Scott Bakerd69e4222019-08-21 16:46:05 -070040 ComponentName string
41 Status string
42 Error string
43}
44
divyadesai19009132020-03-04 12:58:08 +000045// SetLogLevelOpts represents the supported CLI arguments for the loglevel set command
Scott Bakerd69e4222019-08-21 16:46:05 -070046type SetLogLevelOpts struct {
47 OutputOptions
divyadesai19009132020-03-04 12:58:08 +000048 Args struct {
Scott Bakerd69e4222019-08-21 16:46:05 -070049 Level string
50 Component []string
51 } `positional-args:"yes" required:"yes"`
52}
53
divyadesai19009132020-03-04 12:58:08 +000054// ListLogLevelOpts represents the supported CLI arguments for the loglevel list command
55type ListLogLevelsOpts struct {
Scott Bakerd69e4222019-08-21 16:46:05 -070056 ListOutputOptions
57 Args struct {
58 Component []string
59 } `positional-args:"yes" required:"yes"`
60}
61
divyadesai19009132020-03-04 12:58:08 +000062// ClearLogLevelOpts represents the supported CLI arguments for the loglevel clear command
63type ClearLogLevelsOpts struct {
64 OutputOptions
65 Args struct {
66 Component []string
67 } `positional-args:"yes" required:"yes"`
Scott Bakerd69e4222019-08-21 16:46:05 -070068}
69
divyadesai19009132020-03-04 12:58:08 +000070// LogLevelOpts represents the loglevel commands
Scott Bakerd69e4222019-08-21 16:46:05 -070071type LogLevelOpts struct {
divyadesai19009132020-03-04 12:58:08 +000072 SetLogLevel SetLogLevelOpts `command:"set"`
73 ListLogLevels ListLogLevelsOpts `command:"list"`
74 ClearLogLevels ClearLogLevelsOpts `command:"clear"`
Scott Bakerd69e4222019-08-21 16:46:05 -070075}
76
77var logLevelOpts = LogLevelOpts{}
78
79const (
divyadesai19009132020-03-04 12:58:08 +000080 DEFAULT_LOGLEVELS_FORMAT = "table{{ .ComponentName }}\t{{.PackageName}}\t{{.Level}}"
81 DEFAULT_LOGLEVEL_RESULT_FORMAT = "table{{ .ComponentName }}\t{{.Status}}\t{{.Error}}"
Scott Bakerd69e4222019-08-21 16:46:05 -070082)
83
divyadesai19009132020-03-04 12:58:08 +000084// RegisterLogLevelCommands is used to register set,list and clear loglevel of components
Scott Bakerd69e4222019-08-21 16:46:05 -070085func RegisterLogLevelCommands(parent *flags.Parser) {
divyadesai19009132020-03-04 12:58:08 +000086 _, err := parent.AddCommand("loglevel", "loglevel commands", "list, set, clear log levels of components", &logLevelOpts)
Scott Bakerd69e4222019-08-21 16:46:05 -070087 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +000088 Error.Fatalf("Unable to register log level commands with voltctl command parser: %s", err.Error())
Scott Bakerd69e4222019-08-21 16:46:05 -070089 }
90}
91
divyadesai19009132020-03-04 12:58:08 +000092// processComponentListArgs stores the component name and package names given in command arguments to LogLevel
93// It checks the given argument has # key or not, if # is present then split the argument for # then stores first part as component name
94// and second part as package name
95func processComponentListArgs(Components []string) ([]model.LogLevel, error) {
Scott Bakerd69e4222019-08-21 16:46:05 -070096
divyadesai19009132020-03-04 12:58:08 +000097 var logLevelConfig []model.LogLevel
Scott Bakerd69e4222019-08-21 16:46:05 -070098
divyadesai19009132020-03-04 12:58:08 +000099 if len(Components) == 0 {
100 Components = append(Components, defaultComponentName)
Scott Bakerd69e4222019-08-21 16:46:05 -0700101 }
102
divyadesai19009132020-03-04 12:58:08 +0000103 for _, component := range Components {
104 logConfig := model.LogLevel{}
105 val := strings.SplitN(component, "#", 2)
106 if len(val) > 1 {
107 if val[0] == defaultComponentName {
108 return nil, errors.New("global level doesn't support packageName")
Scott Bakerd69e4222019-08-21 16:46:05 -0700109 }
divyadesai19009132020-03-04 12:58:08 +0000110 logConfig.ComponentName = val[0]
111 logConfig.PackageName = strings.ReplaceAll(val[1], "/", "#")
112 } else {
113 logConfig.ComponentName = component
114 logConfig.PackageName = defaultPackageName
Scott Bakerd69e4222019-08-21 16:46:05 -0700115 }
divyadesai19009132020-03-04 12:58:08 +0000116 logLevelConfig = append(logLevelConfig, logConfig)
Scott Bakerd69e4222019-08-21 16:46:05 -0700117 }
divyadesai19009132020-03-04 12:58:08 +0000118 return logLevelConfig, nil
Scott Bakerd69e4222019-08-21 16:46:05 -0700119}
120
divyadesai19009132020-03-04 12:58:08 +0000121// This method set loglevel for components.
122// For example, using below command loglevel can be set for specific component with default packageName
123// voltctl loglevel set level <componentName>
124// For example, using below command loglevel can be set for specific component with specific packageName
125// voltctl loglevel set level <componentName#packageName>
126// For example, using below command loglevel can be set for more than one component for default package and other component for specific packageName
127// voltctl loglevel set level <componentName1#packageName> <componentName2>
Scott Bakerd69e4222019-08-21 16:46:05 -0700128func (options *SetLogLevelOpts) Execute(args []string) error {
divyadesai19009132020-03-04 12:58:08 +0000129 var (
130 logLevelConfig []model.LogLevel
131 err error
132 )
133 ProcessGlobalOptions()
134
135 /*
136 * TODO: VOL-2738
137 * EVIL HACK ALERT
138 * ===============
139 * It would be nice if we could squelch all but fatal log messages from
140 * the underlying libraries because as a CLI client we don't want a
141 * bunch of logs and stack traces output and instead want to deal with
142 * simple error propagation. To work around this, voltha-lib-go logging
143 * is set to fatal and we redirect etcd client logging to a temp file.
144 *
145 * Replacing os.Stderr is used here as opposed to Dup2 because we want
146 * low level panic to be displayed if they occurr. A temp file is used
147 * as opposed to /dev/null because it can't be assumed that /dev/null
148 * exists on all platforms and thus a temp file seems more portable.
149 */
150 log.SetAllLogLevel(log.FatalLevel)
151 saveStderr := os.Stderr
152 if tmpStderr, err := ioutil.TempFile("", ""); err == nil {
153 os.Stderr = tmpStderr
154 defer func() {
155 os.Stderr = saveStderr
156 // Ignore errors on clean up because we can't do
157 // anything anyway.
158 _ = tmpStderr.Close()
159 _ = os.Remove(tmpStderr.Name())
160 }()
Scott Bakerd69e4222019-08-21 16:46:05 -0700161 }
162
divyadesai19009132020-03-04 12:58:08 +0000163 if options.Args.Level != "" {
164 if _, err := log.StringToLogLevel(options.Args.Level); err != nil {
165 return fmt.Errorf("Unknown log level %s. Allowed values are INFO, DEBUG, ERROR, WARN, FATAL", options.Args.Level)
Scott Bakerd69e4222019-08-21 16:46:05 -0700166 }
167 }
168
divyadesai19009132020-03-04 12:58:08 +0000169 logLevelConfig, err = processComponentListArgs(options.Args.Component)
170 if err != nil {
171 return fmt.Errorf(err.Error())
172 }
173
174 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
175 if err != nil {
176 return fmt.Errorf("Unable to create kvstore client %s", err)
177 }
178 defer client.Close()
179
180 // Already error checked during option processing
181 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
182 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
183
184 var output []LogLevelOutput
185
186 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
187 defer cancel()
188
189 for _, lConfig := range logLevelConfig {
190
191 logConfig := cm.InitComponentConfig(lConfig.ComponentName, config.ConfigTypeLogLevel)
192 err := logConfig.Save(ctx, lConfig.PackageName, strings.ToUpper(options.Args.Level))
193 if err != nil {
194 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Failure", Error: err.Error()})
195 } else {
196 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Success"})
197 }
198
199 }
200
Scott Bakerd69e4222019-08-21 16:46:05 -0700201 outputFormat := CharReplacer.Replace(options.Format)
202 if outputFormat == "" {
divyadesai19009132020-03-04 12:58:08 +0000203 outputFormat = GetCommandOptionWithDefault("loglevel-set", "format", DEFAULT_LOGLEVEL_RESULT_FORMAT)
Scott Bakerd69e4222019-08-21 16:46:05 -0700204 }
Scott Bakerd69e4222019-08-21 16:46:05 -0700205 result := CommandResult{
206 Format: format.Format(outputFormat),
207 OutputAs: toOutputType(options.OutputAs),
208 NameLimit: options.NameLimit,
209 Data: output,
210 }
211
212 GenerateOutput(&result)
213 return nil
214}
215
divyadesai19009132020-03-04 12:58:08 +0000216// This method list loglevel for components.
217// For example, using below command loglevel can be list for specific component
218// voltctl loglevel list <componentName>
219// For example, using below command loglevel can be list for all the components with all the packageName
220// voltctl loglevel list
221func (options *ListLogLevelsOpts) Execute(args []string) error {
222
223 var (
224 data []model.LogLevel
225 componentList []string
226 logLevelConfig map[string]string
227 err error
228 )
229 ProcessGlobalOptions()
230
231 /*
232 * TODO: VOL-2738
233 * EVIL HACK ALERT
234 * ===============
235 * It would be nice if we could squelch all but fatal log messages from
236 * the underlying libraries because as a CLI client we don't want a
237 * bunch of logs and stack traces output and instead want to deal with
238 * simple error propagation. To work around this, voltha-lib-go logging
239 * is set to fatal and we redirect etcd client logging to a temp file.
240 *
241 * Replacing os.Stderr is used here as opposed to Dup2 because we want
242 * low level panic to be displayed if they occurr. A temp file is used
243 * as opposed to /dev/null because it can't be assumed that /dev/null
244 * exists on all platforms and thus a temp file seems more portable.
245 */
246 log.SetAllLogLevel(log.FatalLevel)
247 saveStderr := os.Stderr
248 if tmpStderr, err := ioutil.TempFile("", ""); err == nil {
249 os.Stderr = tmpStderr
250 defer func() {
251 os.Stderr = saveStderr
252 // Ignore errors on clean up because we can't do
253 // anything anyway.
254 _ = tmpStderr.Close()
255 _ = os.Remove(tmpStderr.Name())
256 }()
257 }
258
259 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
260 if err != nil {
261 return fmt.Errorf("Unable to create kvstore client %s", err)
262 }
263 defer client.Close()
264
265 // Already error checked during option processing
266 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
267 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
268
269 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
270 defer cancel()
271
Scott Bakerd69e4222019-08-21 16:46:05 -0700272 if len(options.Args.Component) == 0 {
divyadesai19009132020-03-04 12:58:08 +0000273 componentList, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
274 if err != nil {
275 return fmt.Errorf("Unable to retrieve list of voltha components : %s ", err)
276 }
277 } else {
278 componentList = options.Args.Component
Scott Bakerd69e4222019-08-21 16:46:05 -0700279 }
280
divyadesai19009132020-03-04 12:58:08 +0000281 for _, componentName := range componentList {
282 logConfig := cm.InitComponentConfig(componentName, config.ConfigTypeLogLevel)
Scott Bakerd69e4222019-08-21 16:46:05 -0700283
divyadesai19009132020-03-04 12:58:08 +0000284 logLevelConfig, err = logConfig.RetrieveAll(ctx)
285 if err != nil {
286 return fmt.Errorf("Unable to retrieve loglevel configuration for component %s : %s", componentName, err)
287 }
Scott Bakerd69e4222019-08-21 16:46:05 -0700288
divyadesai19009132020-03-04 12:58:08 +0000289 for packageName, level := range logLevelConfig {
290 logLevel := model.LogLevel{}
291 if packageName == "" {
292 continue
Scott Bakerd69e4222019-08-21 16:46:05 -0700293 }
294
divyadesai19009132020-03-04 12:58:08 +0000295 pName := strings.ReplaceAll(packageName, "#", "/")
296 logLevel.PopulateFrom(componentName, pName, level)
297 data = append(data, logLevel)
Scott Bakerd69e4222019-08-21 16:46:05 -0700298 }
299 }
300
301 outputFormat := CharReplacer.Replace(options.Format)
302 if outputFormat == "" {
divyadesai19009132020-03-04 12:58:08 +0000303 outputFormat = GetCommandOptionWithDefault("loglevel-list", "format", DEFAULT_LOGLEVELS_FORMAT)
David Bainbridgea6722342019-10-24 23:55:53 +0000304 }
305 orderBy := options.OrderBy
306 if orderBy == "" {
divyadesai19009132020-03-04 12:58:08 +0000307 orderBy = GetCommandOptionWithDefault("loglevel-list", "order", "")
Scott Bakerd69e4222019-08-21 16:46:05 -0700308 }
309
310 result := CommandResult{
311 Format: format.Format(outputFormat),
312 Filter: options.Filter,
David Bainbridgea6722342019-10-24 23:55:53 +0000313 OrderBy: orderBy,
Scott Bakerd69e4222019-08-21 16:46:05 -0700314 OutputAs: toOutputType(options.OutputAs),
315 NameLimit: options.NameLimit,
316 Data: data,
317 }
318 GenerateOutput(&result)
319 return nil
320}
321
divyadesai19009132020-03-04 12:58:08 +0000322// This method clear loglevel for components.
323// For example, using below command loglevel can be clear for specific component with default packageName
324// voltctl loglevel clear <componentName>
325// For example, using below command loglevel can be clear for specific component with specific packageName
326// voltctl loglevel clear <componentName#packageName>
327func (options *ClearLogLevelsOpts) Execute(args []string) error {
David Bainbridgea6722342019-10-24 23:55:53 +0000328
divyadesai19009132020-03-04 12:58:08 +0000329 var (
330 logLevelConfig []model.LogLevel
331 err error
332 )
333 ProcessGlobalOptions()
Scott Bakerd69e4222019-08-21 16:46:05 -0700334
divyadesai19009132020-03-04 12:58:08 +0000335 /*
336 * TODO: VOL-2738
337 * EVIL HACK ALERT
338 * ===============
339 * It would be nice if we could squelch all but fatal log messages from
340 * the underlying libraries because as a CLI client we don't want a
341 * bunch of logs and stack traces output and instead want to deal with
342 * simple error propagation. To work around this, voltha-lib-go logging
343 * is set to fatal and we redirect etcd client logging to a temp file.
344 *
345 * Replacing os.Stderr is used here as opposed to Dup2 because we want
346 * low level panic to be displayed if they occurr. A temp file is used
347 * as opposed to /dev/null because it can't be assumed that /dev/null
348 * exists on all platforms and thus a temp file seems more portable.
349 */
350 log.SetAllLogLevel(log.FatalLevel)
351 saveStderr := os.Stderr
352 if tmpStderr, err := ioutil.TempFile("", ""); err == nil {
353 os.Stderr = tmpStderr
354 defer func() {
355 os.Stderr = saveStderr
356 // Ignore errors on clean up because we can't do
357 // anything anyway.
358 _ = tmpStderr.Close()
359 _ = os.Remove(tmpStderr.Name())
360 }()
361 }
362
363 logLevelConfig, err = processComponentListArgs(options.Args.Component)
Scott Bakerd69e4222019-08-21 16:46:05 -0700364 if err != nil {
divyadesai19009132020-03-04 12:58:08 +0000365 return fmt.Errorf("%s", err)
Scott Bakerd69e4222019-08-21 16:46:05 -0700366 }
367
divyadesai19009132020-03-04 12:58:08 +0000368 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
369 if err != nil {
370 return fmt.Errorf("Unable to create kvstore client %s", err)
371 }
372 defer client.Close()
373
374 // Already error checked during option processing
375 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
376 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
377
378 var output []LogLevelOutput
379
380 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
381 defer cancel()
382
383 for _, lConfig := range logLevelConfig {
384
385 logConfig := cm.InitComponentConfig(lConfig.ComponentName, config.ConfigTypeLogLevel)
386
387 err := logConfig.Delete(ctx, lConfig.PackageName)
388 if err != nil {
389 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Failure", Error: err.Error()})
390 } else {
391 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Success"})
392 }
Scott Bakerd69e4222019-08-21 16:46:05 -0700393 }
394
divyadesai19009132020-03-04 12:58:08 +0000395 outputFormat := CharReplacer.Replace(options.Format)
396 if outputFormat == "" {
397 outputFormat = GetCommandOptionWithDefault("loglevel-clear", "format", DEFAULT_LOGLEVEL_RESULT_FORMAT)
398 }
Scott Bakerd69e4222019-08-21 16:46:05 -0700399
divyadesai19009132020-03-04 12:58:08 +0000400 result := CommandResult{
401 Format: format.Format(outputFormat),
402 OutputAs: toOutputType(options.OutputAs),
403 NameLimit: options.NameLimit,
404 Data: output,
405 }
Scott Bakerd69e4222019-08-21 16:46:05 -0700406
divyadesai19009132020-03-04 12:58:08 +0000407 GenerateOutput(&result)
408 return nil
Scott Bakerd69e4222019-08-21 16:46:05 -0700409}