blob: 8b603f8497cde88cd5e11953c9c83fb998f424bf [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
243 } else if options.MACAddress != "" {
244 dm["mac_address"] = strings.ToLower(options.MACAddress)
245 }
246 if options.DeviceType != "" {
247 dm["type"] = options.DeviceType
248 }
249
250 conn, err := NewConnection()
251 if err != nil {
252 return err
253 }
254 defer conn.Close()
255
256 descriptor, method, err := GetMethod("device-create")
257 if err != nil {
258 return err
259 }
260
261 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
262 defer cancel()
263
264 h := &RpcEventHandler{
265 Fields: map[string]map[string]interface{}{"voltha.Device": dm},
266 }
267 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
268 if err != nil {
269 return err
270 } else if h.Status != nil && h.Status.Err() != nil {
271 return h.Status.Err()
272 }
273
274 resp, err := dynamic.AsDynamicMessage(h.Response)
275 if err != nil {
276 return err
277 }
278 fmt.Printf("%s\n", resp.GetFieldByName("id").(string))
279
280 return nil
281}
282
283func (options *DeviceDelete) Execute(args []string) error {
284
285 conn, err := NewConnection()
286 if err != nil {
287 return err
288 }
289 defer conn.Close()
290
291 descriptor, method, err := GetMethod("device-delete")
292 if err != nil {
293 return err
294 }
295
296 for _, i := range options.Args.Ids {
297
298 h := &RpcEventHandler{
299 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": i}},
300 }
301 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
302 defer cancel()
303
304 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
305 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000306 Error.Printf("Error while deleting '%s': %s\n", i, err)
Zack Williamse940c7a2019-08-21 14:25:39 -0700307 continue
308 } else if h.Status != nil && h.Status.Err() != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000309 Error.Printf("Error while deleting '%s': %s\n", i, ErrorToString(h.Status.Err()))
Zack Williamse940c7a2019-08-21 14:25:39 -0700310 continue
311 }
312 fmt.Printf("%s\n", i)
313 }
314
315 return nil
316}
317
318func (options *DeviceEnable) Execute(args []string) error {
319 conn, err := NewConnection()
320 if err != nil {
321 return err
322 }
323 defer conn.Close()
324
325 descriptor, method, err := GetMethod("device-enable")
326 if err != nil {
327 return err
328 }
329
330 for _, i := range options.Args.Ids {
331 h := &RpcEventHandler{
332 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": i}},
333 }
334 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
335 defer cancel()
336
337 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
338 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000339 Error.Printf("Error while enabling '%s': %s\n", i, err)
Zack Williamse940c7a2019-08-21 14:25:39 -0700340 continue
341 } else if h.Status != nil && h.Status.Err() != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000342 Error.Printf("Error while enabling '%s': %s\n", i, ErrorToString(h.Status.Err()))
Zack Williamse940c7a2019-08-21 14:25:39 -0700343 continue
344 }
345 fmt.Printf("%s\n", i)
346 }
347
348 return nil
349}
350
351func (options *DeviceDisable) Execute(args []string) error {
352 conn, err := NewConnection()
353 if err != nil {
354 return err
355 }
356 defer conn.Close()
357
358 descriptor, method, err := GetMethod("device-disable")
359 if err != nil {
360 return err
361 }
362
363 for _, i := range options.Args.Ids {
364 h := &RpcEventHandler{
365 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": i}},
366 }
367 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
368 defer cancel()
369
370 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
371 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000372 Error.Printf("Error while disabling '%s': %s\n", i, err)
Zack Williamse940c7a2019-08-21 14:25:39 -0700373 continue
374 } else if h.Status != nil && h.Status.Err() != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000375 Error.Printf("Error while disabling '%s': %s\n", i, ErrorToString(h.Status.Err()))
Zack Williamse940c7a2019-08-21 14:25:39 -0700376 continue
377 }
378 fmt.Printf("%s\n", i)
379 }
380
381 return nil
382}
383
384func (options *DeviceReboot) Execute(args []string) error {
385 conn, err := NewConnection()
386 if err != nil {
387 return err
388 }
389 defer conn.Close()
390
391 descriptor, method, err := GetMethod("device-reboot")
392 if err != nil {
393 return err
394 }
395
396 for _, i := range options.Args.Ids {
397 h := &RpcEventHandler{
398 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": i}},
399 }
400 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
401 defer cancel()
402
403 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
404 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000405 Error.Printf("Error while rebooting '%s': %s\n", i, err)
Zack Williamse940c7a2019-08-21 14:25:39 -0700406 continue
407 } else if h.Status != nil && h.Status.Err() != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000408 Error.Printf("Error while rebooting '%s': %s\n", i, ErrorToString(h.Status.Err()))
Zack Williamse940c7a2019-08-21 14:25:39 -0700409 continue
410 }
411 fmt.Printf("%s\n", i)
412 }
413
414 return nil
415}
416
417func (options *DevicePortList) Execute(args []string) error {
418
419 conn, err := NewConnection()
420 if err != nil {
421 return err
422 }
423 defer conn.Close()
424
425 descriptor, method, err := GetMethod("device-ports")
426 if err != nil {
427 return err
428 }
429
430 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
431 defer cancel()
432
433 h := &RpcEventHandler{
434 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": options.Args.Id}},
435 }
436 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
437 if err != nil {
438 return err
439 }
440
441 if h.Status != nil && h.Status.Err() != nil {
442 return h.Status.Err()
443 }
444
445 d, err := dynamic.AsDynamicMessage(h.Response)
446 if err != nil {
447 return err
448 }
449
450 items, err := d.TryGetFieldByName("items")
451 if err != nil {
452 return err
453 }
454
455 outputFormat := CharReplacer.Replace(options.Format)
456 if outputFormat == "" {
David Bainbridgea6722342019-10-24 23:55:53 +0000457 outputFormat = GetCommandOptionWithDefault("device-ports", "format", DEFAULT_DEVICE_PORTS_FORMAT)
Zack Williamse940c7a2019-08-21 14:25:39 -0700458 }
459 if options.Quiet {
460 outputFormat = "{{.Id}}"
461 }
462
David Bainbridgea6722342019-10-24 23:55:53 +0000463 orderBy := options.OrderBy
464 if orderBy == "" {
465 orderBy = GetCommandOptionWithDefault("device-ports", "order", "")
466 }
467
Zack Williamse940c7a2019-08-21 14:25:39 -0700468 data := make([]model.DevicePort, len(items.([]interface{})))
469 for i, item := range items.([]interface{}) {
470 data[i].PopulateFrom(item.(*dynamic.Message))
471 }
472
473 result := CommandResult{
474 Format: format.Format(outputFormat),
475 Filter: options.Filter,
David Bainbridgea6722342019-10-24 23:55:53 +0000476 OrderBy: orderBy,
Zack Williamse940c7a2019-08-21 14:25:39 -0700477 OutputAs: toOutputType(options.OutputAs),
478 NameLimit: options.NameLimit,
479 Data: data,
480 }
481
482 GenerateOutput(&result)
483 return nil
484}
485
486func (options *DeviceFlowList) Execute(args []string) error {
487 fl := &FlowList{}
488 fl.ListOutputOptions = options.ListOutputOptions
489 fl.Args.Id = string(options.Args.Id)
David Bainbridgea6722342019-10-24 23:55:53 +0000490 fl.Method = "device-flows"
Zack Williamse940c7a2019-08-21 14:25:39 -0700491 return fl.Execute(args)
492}
493
494func (options *DeviceInspect) Execute(args []string) error {
495 if len(args) > 0 {
496 return fmt.Errorf("only a single argument 'DEVICE_ID' can be provided")
497 }
498
499 conn, err := NewConnection()
500 if err != nil {
501 return err
502 }
503 defer conn.Close()
504
505 descriptor, method, err := GetMethod("device-inspect")
506 if err != nil {
507 return err
508 }
509
510 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
511 defer cancel()
512
513 h := &RpcEventHandler{
514 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": options.Args.Id}},
515 }
516 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{"Get-Depth: 2"}, h, h.GetParams)
517 if err != nil {
518 return err
519 } else if h.Status != nil && h.Status.Err() != nil {
520 return h.Status.Err()
521 }
522
523 d, err := dynamic.AsDynamicMessage(h.Response)
524 if err != nil {
525 return err
526 }
527
528 device := &model.Device{}
529 device.PopulateFrom(d)
530
531 outputFormat := CharReplacer.Replace(options.Format)
532 if outputFormat == "" {
David Bainbridgea6722342019-10-24 23:55:53 +0000533 outputFormat = GetCommandOptionWithDefault("device-inspect", "format", DEFAULT_DEVICE_INSPECT_FORMAT)
Zack Williamse940c7a2019-08-21 14:25:39 -0700534 }
535 if options.Quiet {
536 outputFormat = "{{.Id}}"
537 }
538
539 result := CommandResult{
540 Format: format.Format(outputFormat),
541 OutputAs: toOutputType(options.OutputAs),
542 NameLimit: options.NameLimit,
543 Data: device,
544 }
545 GenerateOutput(&result)
546 return nil
547}