blob: 30e0cae42851d472b384d4eb99d4c76397384c5c [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"
Scott Bakerd69e4222019-08-21 16:46:05 -070028 "strings"
29)
30
divyadesai19009132020-03-04 12:58:08 +000031const (
32 defaultComponentName = "global"
33 defaultPackageName = "default"
34)
35
36// LogLevelOutput represents the output structure for the loglevel
37type LogLevelOutput struct {
Scott Bakerd69e4222019-08-21 16:46:05 -070038 ComponentName string
39 Status string
40 Error string
41}
42
divyadesai19009132020-03-04 12:58:08 +000043// SetLogLevelOpts represents the supported CLI arguments for the loglevel set command
Scott Bakerd69e4222019-08-21 16:46:05 -070044type SetLogLevelOpts struct {
45 OutputOptions
divyadesai19009132020-03-04 12:58:08 +000046 Args struct {
Scott Bakerd69e4222019-08-21 16:46:05 -070047 Level string
48 Component []string
49 } `positional-args:"yes" required:"yes"`
50}
51
divyadesai19009132020-03-04 12:58:08 +000052// ListLogLevelOpts represents the supported CLI arguments for the loglevel list command
53type ListLogLevelsOpts struct {
Scott Bakerd69e4222019-08-21 16:46:05 -070054 ListOutputOptions
55 Args struct {
56 Component []string
57 } `positional-args:"yes" required:"yes"`
58}
59
divyadesai19009132020-03-04 12:58:08 +000060// ClearLogLevelOpts represents the supported CLI arguments for the loglevel clear command
61type ClearLogLevelsOpts struct {
62 OutputOptions
63 Args struct {
64 Component []string
65 } `positional-args:"yes" required:"yes"`
Scott Bakerd69e4222019-08-21 16:46:05 -070066}
67
divyadesai19009132020-03-04 12:58:08 +000068// LogLevelOpts represents the loglevel commands
Scott Bakerd69e4222019-08-21 16:46:05 -070069type LogLevelOpts struct {
divyadesai19009132020-03-04 12:58:08 +000070 SetLogLevel SetLogLevelOpts `command:"set"`
71 ListLogLevels ListLogLevelsOpts `command:"list"`
72 ClearLogLevels ClearLogLevelsOpts `command:"clear"`
Scott Bakerd69e4222019-08-21 16:46:05 -070073}
74
75var logLevelOpts = LogLevelOpts{}
76
77const (
divyadesai19009132020-03-04 12:58:08 +000078 DEFAULT_LOGLEVELS_FORMAT = "table{{ .ComponentName }}\t{{.PackageName}}\t{{.Level}}"
79 DEFAULT_LOGLEVEL_RESULT_FORMAT = "table{{ .ComponentName }}\t{{.Status}}\t{{.Error}}"
Scott Bakerd69e4222019-08-21 16:46:05 -070080)
81
divyadesai19009132020-03-04 12:58:08 +000082// RegisterLogLevelCommands is used to register set,list and clear loglevel of components
Scott Bakerd69e4222019-08-21 16:46:05 -070083func RegisterLogLevelCommands(parent *flags.Parser) {
divyadesai19009132020-03-04 12:58:08 +000084 _, err := parent.AddCommand("loglevel", "loglevel commands", "list, set, clear log levels of components", &logLevelOpts)
Scott Bakerd69e4222019-08-21 16:46:05 -070085 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +000086 Error.Fatalf("Unable to register log level commands with voltctl command parser: %s", err.Error())
Scott Bakerd69e4222019-08-21 16:46:05 -070087 }
88}
89
divyadesai19009132020-03-04 12:58:08 +000090// processComponentListArgs stores the component name and package names given in command arguments to LogLevel
91// It checks the given argument has # key or not, if # is present then split the argument for # then stores first part as component name
92// and second part as package name
93func processComponentListArgs(Components []string) ([]model.LogLevel, error) {
Scott Bakerd69e4222019-08-21 16:46:05 -070094
divyadesai19009132020-03-04 12:58:08 +000095 var logLevelConfig []model.LogLevel
Scott Bakerd69e4222019-08-21 16:46:05 -070096
divyadesai19009132020-03-04 12:58:08 +000097 if len(Components) == 0 {
98 Components = append(Components, defaultComponentName)
Scott Bakerd69e4222019-08-21 16:46:05 -070099 }
100
divyadesai19009132020-03-04 12:58:08 +0000101 for _, component := range Components {
102 logConfig := model.LogLevel{}
103 val := strings.SplitN(component, "#", 2)
divyadesai6e28d302020-03-16 08:39:16 +0000104
105 if strings.Contains(val[0], "/") {
106 return nil, errors.New("the component name '" + val[0] + "' contains an invalid character '/'")
107 }
108
divyadesai19009132020-03-04 12:58:08 +0000109 if len(val) > 1 {
110 if val[0] == defaultComponentName {
111 return nil, errors.New("global level doesn't support packageName")
Scott Bakerd69e4222019-08-21 16:46:05 -0700112 }
divyadesai19009132020-03-04 12:58:08 +0000113 logConfig.ComponentName = val[0]
114 logConfig.PackageName = strings.ReplaceAll(val[1], "/", "#")
115 } else {
116 logConfig.ComponentName = component
117 logConfig.PackageName = defaultPackageName
Scott Bakerd69e4222019-08-21 16:46:05 -0700118 }
divyadesai19009132020-03-04 12:58:08 +0000119 logLevelConfig = append(logLevelConfig, logConfig)
Scott Bakerd69e4222019-08-21 16:46:05 -0700120 }
divyadesai19009132020-03-04 12:58:08 +0000121 return logLevelConfig, nil
Scott Bakerd69e4222019-08-21 16:46:05 -0700122}
123
divyadesai19009132020-03-04 12:58:08 +0000124// This method set loglevel for components.
125// For example, using below command loglevel can be set for specific component with default packageName
126// voltctl loglevel set level <componentName>
127// For example, using below command loglevel can be set for specific component with specific packageName
128// voltctl loglevel set level <componentName#packageName>
129// For example, using below command loglevel can be set for more than one component for default package and other component for specific packageName
130// voltctl loglevel set level <componentName1#packageName> <componentName2>
Scott Bakerd69e4222019-08-21 16:46:05 -0700131func (options *SetLogLevelOpts) Execute(args []string) error {
divyadesai19009132020-03-04 12:58:08 +0000132 var (
133 logLevelConfig []model.LogLevel
134 err error
135 )
136 ProcessGlobalOptions()
137
divyadesai19009132020-03-04 12:58:08 +0000138 log.SetAllLogLevel(log.FatalLevel)
Scott Bakerd69e4222019-08-21 16:46:05 -0700139
divyadesai19009132020-03-04 12:58:08 +0000140 if options.Args.Level != "" {
141 if _, err := log.StringToLogLevel(options.Args.Level); err != nil {
divyadesai6e28d302020-03-16 08:39:16 +0000142 return fmt.Errorf("Unknown log level '%s'. Allowed values are DEBUG, INFO, WARN, ERROR, FATAL", options.Args.Level)
Scott Bakerd69e4222019-08-21 16:46:05 -0700143 }
144 }
145
divyadesai19009132020-03-04 12:58:08 +0000146 logLevelConfig, err = processComponentListArgs(options.Args.Component)
147 if err != nil {
148 return fmt.Errorf(err.Error())
149 }
150
Rohan Agrawalc558b1d2020-04-15 16:54:20 +0000151 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), log.FatalLevel)
divyadesai19009132020-03-04 12:58:08 +0000152 if err != nil {
153 return fmt.Errorf("Unable to create kvstore client %s", err)
154 }
155 defer client.Close()
156
157 // Already error checked during option processing
158 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
159 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
160
161 var output []LogLevelOutput
162
163 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
164 defer cancel()
165
166 for _, lConfig := range logLevelConfig {
167
168 logConfig := cm.InitComponentConfig(lConfig.ComponentName, config.ConfigTypeLogLevel)
169 err := logConfig.Save(ctx, lConfig.PackageName, strings.ToUpper(options.Args.Level))
170 if err != nil {
171 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Failure", Error: err.Error()})
172 } else {
173 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Success"})
174 }
175
176 }
177
Scott Bakerd69e4222019-08-21 16:46:05 -0700178 outputFormat := CharReplacer.Replace(options.Format)
179 if outputFormat == "" {
divyadesai19009132020-03-04 12:58:08 +0000180 outputFormat = GetCommandOptionWithDefault("loglevel-set", "format", DEFAULT_LOGLEVEL_RESULT_FORMAT)
Scott Bakerd69e4222019-08-21 16:46:05 -0700181 }
Scott Bakerd69e4222019-08-21 16:46:05 -0700182 result := CommandResult{
183 Format: format.Format(outputFormat),
184 OutputAs: toOutputType(options.OutputAs),
185 NameLimit: options.NameLimit,
186 Data: output,
187 }
188
189 GenerateOutput(&result)
190 return nil
191}
192
divyadesai19009132020-03-04 12:58:08 +0000193// This method list loglevel for components.
194// For example, using below command loglevel can be list for specific component
195// voltctl loglevel list <componentName>
196// For example, using below command loglevel can be list for all the components with all the packageName
197// voltctl loglevel list
198func (options *ListLogLevelsOpts) Execute(args []string) error {
199
200 var (
David K. Bainbridge4bbad142020-03-11 11:55:39 -0700201 // Initialize to empty as opposed to nil so that -o json will
202 // display empty list and not null VOL-2742
203 data []model.LogLevel = []model.LogLevel{}
divyadesai19009132020-03-04 12:58:08 +0000204 componentList []string
205 logLevelConfig map[string]string
206 err error
207 )
208 ProcessGlobalOptions()
209
divyadesai19009132020-03-04 12:58:08 +0000210 log.SetAllLogLevel(log.FatalLevel)
divyadesai19009132020-03-04 12:58:08 +0000211
Rohan Agrawalc558b1d2020-04-15 16:54:20 +0000212 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), log.FatalLevel)
divyadesai19009132020-03-04 12:58:08 +0000213 if err != nil {
214 return fmt.Errorf("Unable to create kvstore client %s", err)
215 }
216 defer client.Close()
217
218 // Already error checked during option processing
219 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
220 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
221
222 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
223 defer cancel()
224
Scott Bakerd69e4222019-08-21 16:46:05 -0700225 if len(options.Args.Component) == 0 {
divyadesai19009132020-03-04 12:58:08 +0000226 componentList, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
227 if err != nil {
228 return fmt.Errorf("Unable to retrieve list of voltha components : %s ", err)
229 }
230 } else {
231 componentList = options.Args.Component
Scott Bakerd69e4222019-08-21 16:46:05 -0700232 }
233
divyadesai19009132020-03-04 12:58:08 +0000234 for _, componentName := range componentList {
235 logConfig := cm.InitComponentConfig(componentName, config.ConfigTypeLogLevel)
Scott Bakerd69e4222019-08-21 16:46:05 -0700236
divyadesai19009132020-03-04 12:58:08 +0000237 logLevelConfig, err = logConfig.RetrieveAll(ctx)
238 if err != nil {
239 return fmt.Errorf("Unable to retrieve loglevel configuration for component %s : %s", componentName, err)
240 }
Scott Bakerd69e4222019-08-21 16:46:05 -0700241
divyadesai19009132020-03-04 12:58:08 +0000242 for packageName, level := range logLevelConfig {
243 logLevel := model.LogLevel{}
244 if packageName == "" {
245 continue
Scott Bakerd69e4222019-08-21 16:46:05 -0700246 }
247
divyadesai19009132020-03-04 12:58:08 +0000248 pName := strings.ReplaceAll(packageName, "#", "/")
249 logLevel.PopulateFrom(componentName, pName, level)
250 data = append(data, logLevel)
Scott Bakerd69e4222019-08-21 16:46:05 -0700251 }
252 }
253
254 outputFormat := CharReplacer.Replace(options.Format)
255 if outputFormat == "" {
divyadesai19009132020-03-04 12:58:08 +0000256 outputFormat = GetCommandOptionWithDefault("loglevel-list", "format", DEFAULT_LOGLEVELS_FORMAT)
David Bainbridgea6722342019-10-24 23:55:53 +0000257 }
258 orderBy := options.OrderBy
259 if orderBy == "" {
divyadesai19009132020-03-04 12:58:08 +0000260 orderBy = GetCommandOptionWithDefault("loglevel-list", "order", "")
Scott Bakerd69e4222019-08-21 16:46:05 -0700261 }
262
263 result := CommandResult{
264 Format: format.Format(outputFormat),
265 Filter: options.Filter,
David Bainbridgea6722342019-10-24 23:55:53 +0000266 OrderBy: orderBy,
Scott Bakerd69e4222019-08-21 16:46:05 -0700267 OutputAs: toOutputType(options.OutputAs),
268 NameLimit: options.NameLimit,
269 Data: data,
270 }
271 GenerateOutput(&result)
272 return nil
273}
274
divyadesai19009132020-03-04 12:58:08 +0000275// This method clear loglevel for components.
276// For example, using below command loglevel can be clear for specific component with default packageName
277// voltctl loglevel clear <componentName>
278// For example, using below command loglevel can be clear for specific component with specific packageName
279// voltctl loglevel clear <componentName#packageName>
280func (options *ClearLogLevelsOpts) Execute(args []string) error {
David Bainbridgea6722342019-10-24 23:55:53 +0000281
divyadesai19009132020-03-04 12:58:08 +0000282 var (
283 logLevelConfig []model.LogLevel
284 err error
285 )
286 ProcessGlobalOptions()
Scott Bakerd69e4222019-08-21 16:46:05 -0700287
divyadesai19009132020-03-04 12:58:08 +0000288 log.SetAllLogLevel(log.FatalLevel)
divyadesai19009132020-03-04 12:58:08 +0000289
290 logLevelConfig, err = processComponentListArgs(options.Args.Component)
Scott Bakerd69e4222019-08-21 16:46:05 -0700291 if err != nil {
divyadesai19009132020-03-04 12:58:08 +0000292 return fmt.Errorf("%s", err)
Scott Bakerd69e4222019-08-21 16:46:05 -0700293 }
294
Rohan Agrawalc558b1d2020-04-15 16:54:20 +0000295 client, err := kvstore.NewEtcdClient(GlobalConfig.KvStore, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()), log.FatalLevel)
divyadesai19009132020-03-04 12:58:08 +0000296 if err != nil {
297 return fmt.Errorf("Unable to create kvstore client %s", err)
298 }
299 defer client.Close()
300
301 // Already error checked during option processing
302 host, port, _ := splitEndpoint(GlobalConfig.KvStore, defaultKvHost, defaultKvPort)
303 cm := config.NewConfigManager(client, supportedKvStoreType, host, port, int(GlobalConfig.KvStoreConfig.Timeout.Seconds()))
304
305 var output []LogLevelOutput
306
307 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
308 defer cancel()
309
310 for _, lConfig := range logLevelConfig {
311
divyadesai6e28d302020-03-16 08:39:16 +0000312 if lConfig.ComponentName == defaultComponentName {
313 return fmt.Errorf("The global default loglevel cannot be cleared.")
314 }
315
divyadesai19009132020-03-04 12:58:08 +0000316 logConfig := cm.InitComponentConfig(lConfig.ComponentName, config.ConfigTypeLogLevel)
317
318 err := logConfig.Delete(ctx, lConfig.PackageName)
319 if err != nil {
320 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Failure", Error: err.Error()})
321 } else {
322 output = append(output, LogLevelOutput{ComponentName: lConfig.ComponentName, Status: "Success"})
323 }
Scott Bakerd69e4222019-08-21 16:46:05 -0700324 }
325
divyadesai19009132020-03-04 12:58:08 +0000326 outputFormat := CharReplacer.Replace(options.Format)
327 if outputFormat == "" {
328 outputFormat = GetCommandOptionWithDefault("loglevel-clear", "format", DEFAULT_LOGLEVEL_RESULT_FORMAT)
329 }
Scott Bakerd69e4222019-08-21 16:46:05 -0700330
divyadesai19009132020-03-04 12:58:08 +0000331 result := CommandResult{
332 Format: format.Format(outputFormat),
333 OutputAs: toOutputType(options.OutputAs),
334 NameLimit: options.NameLimit,
335 Data: output,
336 }
Scott Bakerd69e4222019-08-21 16:46:05 -0700337
divyadesai19009132020-03-04 12:58:08 +0000338 GenerateOutput(&result)
339 return nil
Scott Bakerd69e4222019-08-21 16:46:05 -0700340}