blob: 36c4762c05ffd00fba3cf338c6b91b532c7777d3 [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -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"
20 "fmt"
Zack Williamse940c7a2019-08-21 14:25:39 -070021 "github.com/fullstorydev/grpcurl"
22 flags "github.com/jessevdk/go-flags"
23 "github.com/jhump/protoreflect/dynamic"
Scott Baker2b0ad652019-08-21 14:57:07 -070024 "github.com/opencord/voltctl/pkg/format"
25 "github.com/opencord/voltctl/pkg/model"
Zack Williamse940c7a2019-08-21 14:25:39 -070026 "strings"
27)
28
29const (
30 DEFAULT_DEVICE_FORMAT = "table{{ .Id }}\t{{.Type}}\t{{.Root}}\t{{.ParentId}}\t{{.SerialNumber}}\t{{.Vlan}}\t{{.AdminState}}\t{{.OperStatus}}\t{{.ConnectStatus}}"
31 DEFAULT_DEVICE_PORTS_FORMAT = "table{{.PortNo}}\t{{.Label}}\t{{.Type}}\t{{.AdminState}}\t{{.OperStatus}}\t{{.DeviceId}}\t{{.Peers}}"
32 DEFAULT_DEVICE_INSPECT_FORMAT = `ID: {{.Id}}
33 TYPE: {{.Type}}
34 ROOT: {{.Root}}
35 PARENTID: {{.ParentId}}
36 SERIALNUMBER: {{.SerialNumber}}
37 VLAN: {{.Vlan}}
38 ADMINSTATE: {{.AdminState}}
39 OPERSTATUS: {{.OperStatus}}
40 CONNECTSTATUS: {{.ConnectStatus}}`
41)
42
43type DeviceList struct {
44 ListOutputOptions
45}
46
47type DeviceCreate struct {
48 DeviceType string `short:"t" long:"devicetype" default:"simulated_olt" description:"Device type"`
49 MACAddress string `short:"m" long:"macaddress" default:"00:0c:e2:31:40:00" description:"MAC Address"`
50 IPAddress string `short:"i" long:"ipaddress" default:"" description:"IP Address"`
51 HostAndPort string `short:"H" long:"hostandport" default:"" description:"Host and port"`
52}
53
54type DeviceId string
55
56type DeviceDelete struct {
57 Args struct {
58 Ids []DeviceId `positional-arg-name:"DEVICE_ID" required:"yes"`
59 } `positional-args:"yes"`
60}
61
62type DeviceEnable struct {
63 Args struct {
64 Ids []DeviceId `positional-arg-name:"DEVICE_ID" required:"yes"`
65 } `positional-args:"yes"`
66}
67
68type DeviceDisable struct {
69 Args struct {
70 Ids []DeviceId `positional-arg-name:"DEVICE_ID" required:"yes"`
71 } `positional-args:"yes"`
72}
73
74type DeviceReboot struct {
75 Args struct {
76 Ids []DeviceId `positional-arg-name:"DEVICE_ID" required:"yes"`
77 } `positional-args:"yes"`
78}
79
80type DeviceFlowList struct {
81 ListOutputOptions
82 Args struct {
83 Id DeviceId `positional-arg-name:"DEVICE_ID" required:"yes"`
84 } `positional-args:"yes"`
85}
86
87type DevicePortList struct {
88 ListOutputOptions
89 Args struct {
90 Id DeviceId `positional-arg-name:"DEVICE_ID" required:"yes"`
91 } `positional-args:"yes"`
92}
93
94type DeviceInspect struct {
95 OutputOptionsJson
96 Args struct {
97 Id DeviceId `positional-arg-name:"DEVICE_ID" required:"yes"`
98 } `positional-args:"yes"`
99}
100
101type DeviceOpts struct {
102 List DeviceList `command:"list"`
103 Create DeviceCreate `command:"create"`
104 Delete DeviceDelete `command:"delete"`
105 Enable DeviceEnable `command:"enable"`
106 Disable DeviceDisable `command:"disable"`
107 Flows DeviceFlowList `command:"flows"`
108 Ports DevicePortList `command:"ports"`
109 Inspect DeviceInspect `command:"inspect"`
110 Reboot DeviceReboot `command:"reboot"`
111}
112
113var deviceOpts = DeviceOpts{}
114
115func RegisterDeviceCommands(parser *flags.Parser) {
David Bainbridge12f036f2019-10-15 22:09:04 +0000116 if _, err := parser.AddCommand("device", "device commands", "Commands to query and manipulate VOLTHA devices", &deviceOpts); err != nil {
David Bainbridgea6722342019-10-24 23:55:53 +0000117 Error.Fatalf("Unexpected error while attempting to register device commands : %s", err)
David Bainbridge12f036f2019-10-15 22:09:04 +0000118 }
Zack Williamse940c7a2019-08-21 14:25:39 -0700119}
120
121func (i *DeviceId) Complete(match string) []flags.Completion {
122 conn, err := NewConnection()
123 if err != nil {
124 return nil
125 }
126 defer conn.Close()
127
128 descriptor, method, err := GetMethod("device-list")
129 if err != nil {
130 return nil
131 }
132
133 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
134 defer cancel()
135
136 h := &RpcEventHandler{}
137 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
138 if err != nil {
139 return nil
140 }
141
142 if h.Status != nil && h.Status.Err() != nil {
143 return nil
144 }
145
146 d, err := dynamic.AsDynamicMessage(h.Response)
147 if err != nil {
148 return nil
149 }
150
151 items, err := d.TryGetFieldByName("items")
152 if err != nil {
153 return nil
154 }
155
156 list := make([]flags.Completion, 0)
157 for _, item := range items.([]interface{}) {
158 val := item.(*dynamic.Message)
159 id := val.GetFieldByName("id").(string)
160 if strings.HasPrefix(id, match) {
161 list = append(list, flags.Completion{Item: id})
162 }
163 }
164
165 return list
166}
167
168func (options *DeviceList) Execute(args []string) error {
169
170 conn, err := NewConnection()
171 if err != nil {
172 return err
173 }
174 defer conn.Close()
175
176 descriptor, method, err := GetMethod("device-list")
177 if err != nil {
178 return err
179 }
180
181 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
182 defer cancel()
183
184 h := &RpcEventHandler{}
185 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
186 if err != nil {
187 return err
188 }
189
190 if h.Status != nil && h.Status.Err() != nil {
191 return h.Status.Err()
192 }
193
194 d, err := dynamic.AsDynamicMessage(h.Response)
195 if err != nil {
196 return err
197 }
198
199 items, err := d.TryGetFieldByName("items")
200 if err != nil {
201 return err
202 }
203
204 outputFormat := CharReplacer.Replace(options.Format)
205 if outputFormat == "" {
David Bainbridgea6722342019-10-24 23:55:53 +0000206 outputFormat = GetCommandOptionWithDefault("device-list", "format", DEFAULT_DEVICE_FORMAT)
Zack Williamse940c7a2019-08-21 14:25:39 -0700207 }
208 if options.Quiet {
209 outputFormat = "{{.Id}}"
210 }
211
David Bainbridgea6722342019-10-24 23:55:53 +0000212 orderBy := options.OrderBy
213 if orderBy == "" {
214 orderBy = GetCommandOptionWithDefault("device-list", "order", "")
215 }
216
Zack Williamse940c7a2019-08-21 14:25:39 -0700217 data := make([]model.Device, len(items.([]interface{})))
218 for i, item := range items.([]interface{}) {
219 val := item.(*dynamic.Message)
220 data[i].PopulateFrom(val)
221 }
222
223 result := CommandResult{
224 Format: format.Format(outputFormat),
225 Filter: options.Filter,
David Bainbridgea6722342019-10-24 23:55:53 +0000226 OrderBy: orderBy,
Zack Williamse940c7a2019-08-21 14:25:39 -0700227 OutputAs: toOutputType(options.OutputAs),
228 NameLimit: options.NameLimit,
229 Data: data,
230 }
231
232 GenerateOutput(&result)
233 return nil
234}
235
236func (options *DeviceCreate) Execute(args []string) error {
237
238 dm := make(map[string]interface{})
239 if options.HostAndPort != "" {
240 dm["host_and_port"] = options.HostAndPort
241 } else if options.IPAddress != "" {
242 dm["ipv4_address"] = options.IPAddress
Hardik Windlassce1de342020-02-04 21:58:07 +0000243 }
244 if options.MACAddress != "" {
Zack Williamse940c7a2019-08-21 14:25:39 -0700245 dm["mac_address"] = strings.ToLower(options.MACAddress)
246 }
247 if options.DeviceType != "" {
248 dm["type"] = options.DeviceType
249 }
250
251 conn, err := NewConnection()
252 if err != nil {
253 return err
254 }
255 defer conn.Close()
256
257 descriptor, method, err := GetMethod("device-create")
258 if err != nil {
259 return err
260 }
261
262 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
263 defer cancel()
264
265 h := &RpcEventHandler{
266 Fields: map[string]map[string]interface{}{"voltha.Device": dm},
267 }
268 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
269 if err != nil {
270 return err
271 } else if h.Status != nil && h.Status.Err() != nil {
272 return h.Status.Err()
273 }
274
275 resp, err := dynamic.AsDynamicMessage(h.Response)
276 if err != nil {
277 return err
278 }
279 fmt.Printf("%s\n", resp.GetFieldByName("id").(string))
280
281 return nil
282}
283
284func (options *DeviceDelete) Execute(args []string) error {
285
286 conn, err := NewConnection()
287 if err != nil {
288 return err
289 }
290 defer conn.Close()
291
292 descriptor, method, err := GetMethod("device-delete")
293 if err != nil {
294 return err
295 }
296
297 for _, i := range options.Args.Ids {
298
299 h := &RpcEventHandler{
300 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": i}},
301 }
302 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
303 defer cancel()
304
305 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
306 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000307 Error.Printf("Error while deleting '%s': %s\n", i, err)
Zack Williamse940c7a2019-08-21 14:25:39 -0700308 continue
309 } else if h.Status != nil && h.Status.Err() != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000310 Error.Printf("Error while deleting '%s': %s\n", i, ErrorToString(h.Status.Err()))
Zack Williamse940c7a2019-08-21 14:25:39 -0700311 continue
312 }
313 fmt.Printf("%s\n", i)
314 }
315
316 return nil
317}
318
319func (options *DeviceEnable) Execute(args []string) error {
320 conn, err := NewConnection()
321 if err != nil {
322 return err
323 }
324 defer conn.Close()
325
326 descriptor, method, err := GetMethod("device-enable")
327 if err != nil {
328 return err
329 }
330
331 for _, i := range options.Args.Ids {
332 h := &RpcEventHandler{
333 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": i}},
334 }
335 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
336 defer cancel()
337
338 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
339 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000340 Error.Printf("Error while enabling '%s': %s\n", i, err)
Zack Williamse940c7a2019-08-21 14:25:39 -0700341 continue
342 } else if h.Status != nil && h.Status.Err() != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000343 Error.Printf("Error while enabling '%s': %s\n", i, ErrorToString(h.Status.Err()))
Zack Williamse940c7a2019-08-21 14:25:39 -0700344 continue
345 }
346 fmt.Printf("%s\n", i)
347 }
348
349 return nil
350}
351
352func (options *DeviceDisable) Execute(args []string) error {
353 conn, err := NewConnection()
354 if err != nil {
355 return err
356 }
357 defer conn.Close()
358
359 descriptor, method, err := GetMethod("device-disable")
360 if err != nil {
361 return err
362 }
363
364 for _, i := range options.Args.Ids {
365 h := &RpcEventHandler{
366 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": i}},
367 }
368 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
369 defer cancel()
370
371 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
372 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000373 Error.Printf("Error while disabling '%s': %s\n", i, err)
Zack Williamse940c7a2019-08-21 14:25:39 -0700374 continue
375 } else if h.Status != nil && h.Status.Err() != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000376 Error.Printf("Error while disabling '%s': %s\n", i, ErrorToString(h.Status.Err()))
Zack Williamse940c7a2019-08-21 14:25:39 -0700377 continue
378 }
379 fmt.Printf("%s\n", i)
380 }
381
382 return nil
383}
384
385func (options *DeviceReboot) Execute(args []string) error {
386 conn, err := NewConnection()
387 if err != nil {
388 return err
389 }
390 defer conn.Close()
391
392 descriptor, method, err := GetMethod("device-reboot")
393 if err != nil {
394 return err
395 }
396
397 for _, i := range options.Args.Ids {
398 h := &RpcEventHandler{
399 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": i}},
400 }
401 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
402 defer cancel()
403
404 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
405 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000406 Error.Printf("Error while rebooting '%s': %s\n", i, err)
Zack Williamse940c7a2019-08-21 14:25:39 -0700407 continue
408 } else if h.Status != nil && h.Status.Err() != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000409 Error.Printf("Error while rebooting '%s': %s\n", i, ErrorToString(h.Status.Err()))
Zack Williamse940c7a2019-08-21 14:25:39 -0700410 continue
411 }
412 fmt.Printf("%s\n", i)
413 }
414
415 return nil
416}
417
418func (options *DevicePortList) Execute(args []string) error {
419
420 conn, err := NewConnection()
421 if err != nil {
422 return err
423 }
424 defer conn.Close()
425
426 descriptor, method, err := GetMethod("device-ports")
427 if err != nil {
428 return err
429 }
430
431 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
432 defer cancel()
433
434 h := &RpcEventHandler{
435 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": options.Args.Id}},
436 }
437 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
438 if err != nil {
439 return err
440 }
441
442 if h.Status != nil && h.Status.Err() != nil {
443 return h.Status.Err()
444 }
445
446 d, err := dynamic.AsDynamicMessage(h.Response)
447 if err != nil {
448 return err
449 }
450
451 items, err := d.TryGetFieldByName("items")
452 if err != nil {
453 return err
454 }
455
456 outputFormat := CharReplacer.Replace(options.Format)
457 if outputFormat == "" {
David Bainbridgea6722342019-10-24 23:55:53 +0000458 outputFormat = GetCommandOptionWithDefault("device-ports", "format", DEFAULT_DEVICE_PORTS_FORMAT)
Zack Williamse940c7a2019-08-21 14:25:39 -0700459 }
460 if options.Quiet {
461 outputFormat = "{{.Id}}"
462 }
463
David Bainbridgea6722342019-10-24 23:55:53 +0000464 orderBy := options.OrderBy
465 if orderBy == "" {
466 orderBy = GetCommandOptionWithDefault("device-ports", "order", "")
467 }
468
Zack Williamse940c7a2019-08-21 14:25:39 -0700469 data := make([]model.DevicePort, len(items.([]interface{})))
470 for i, item := range items.([]interface{}) {
471 data[i].PopulateFrom(item.(*dynamic.Message))
472 }
473
474 result := CommandResult{
475 Format: format.Format(outputFormat),
476 Filter: options.Filter,
David Bainbridgea6722342019-10-24 23:55:53 +0000477 OrderBy: orderBy,
Zack Williamse940c7a2019-08-21 14:25:39 -0700478 OutputAs: toOutputType(options.OutputAs),
479 NameLimit: options.NameLimit,
480 Data: data,
481 }
482
483 GenerateOutput(&result)
484 return nil
485}
486
487func (options *DeviceFlowList) Execute(args []string) error {
488 fl := &FlowList{}
489 fl.ListOutputOptions = options.ListOutputOptions
490 fl.Args.Id = string(options.Args.Id)
David Bainbridgea6722342019-10-24 23:55:53 +0000491 fl.Method = "device-flows"
Zack Williamse940c7a2019-08-21 14:25:39 -0700492 return fl.Execute(args)
493}
494
495func (options *DeviceInspect) Execute(args []string) error {
496 if len(args) > 0 {
497 return fmt.Errorf("only a single argument 'DEVICE_ID' can be provided")
498 }
499
500 conn, err := NewConnection()
501 if err != nil {
502 return err
503 }
504 defer conn.Close()
505
506 descriptor, method, err := GetMethod("device-inspect")
507 if err != nil {
508 return err
509 }
510
511 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
512 defer cancel()
513
514 h := &RpcEventHandler{
515 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": options.Args.Id}},
516 }
517 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{"Get-Depth: 2"}, h, h.GetParams)
518 if err != nil {
519 return err
520 } else if h.Status != nil && h.Status.Err() != nil {
521 return h.Status.Err()
522 }
523
524 d, err := dynamic.AsDynamicMessage(h.Response)
525 if err != nil {
526 return err
527 }
528
529 device := &model.Device{}
530 device.PopulateFrom(d)
531
532 outputFormat := CharReplacer.Replace(options.Format)
533 if outputFormat == "" {
David Bainbridgea6722342019-10-24 23:55:53 +0000534 outputFormat = GetCommandOptionWithDefault("device-inspect", "format", DEFAULT_DEVICE_INSPECT_FORMAT)
Zack Williamse940c7a2019-08-21 14:25:39 -0700535 }
536 if options.Quiet {
537 outputFormat = "{{.Id}}"
538 }
539
540 result := CommandResult{
541 Format: format.Format(outputFormat),
542 OutputAs: toOutputType(options.OutputAs),
543 NameLimit: options.NameLimit,
544 Data: device,
545 }
546 GenerateOutput(&result)
547 return nil
548}