blob: a6836cf93872347b053b1c96778e6096fbbc3bd2 [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"
21 "github.com/ciena/voltctl/pkg/format"
22 "github.com/ciena/voltctl/pkg/model"
23 "github.com/fullstorydev/grpcurl"
24 flags "github.com/jessevdk/go-flags"
25 "github.com/jhump/protoreflect/dynamic"
26 "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) {
116 parser.AddCommand("device", "device commands", "Commands to query and manipulate VOLTHA devices", &deviceOpts)
117}
118
119func (i *DeviceId) Complete(match string) []flags.Completion {
120 conn, err := NewConnection()
121 if err != nil {
122 return nil
123 }
124 defer conn.Close()
125
126 descriptor, method, err := GetMethod("device-list")
127 if err != nil {
128 return nil
129 }
130
131 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
132 defer cancel()
133
134 h := &RpcEventHandler{}
135 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
136 if err != nil {
137 return nil
138 }
139
140 if h.Status != nil && h.Status.Err() != nil {
141 return nil
142 }
143
144 d, err := dynamic.AsDynamicMessage(h.Response)
145 if err != nil {
146 return nil
147 }
148
149 items, err := d.TryGetFieldByName("items")
150 if err != nil {
151 return nil
152 }
153
154 list := make([]flags.Completion, 0)
155 for _, item := range items.([]interface{}) {
156 val := item.(*dynamic.Message)
157 id := val.GetFieldByName("id").(string)
158 if strings.HasPrefix(id, match) {
159 list = append(list, flags.Completion{Item: id})
160 }
161 }
162
163 return list
164}
165
166func (options *DeviceList) Execute(args []string) error {
167
168 conn, err := NewConnection()
169 if err != nil {
170 return err
171 }
172 defer conn.Close()
173
174 descriptor, method, err := GetMethod("device-list")
175 if err != nil {
176 return err
177 }
178
179 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
180 defer cancel()
181
182 h := &RpcEventHandler{}
183 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
184 if err != nil {
185 return err
186 }
187
188 if h.Status != nil && h.Status.Err() != nil {
189 return h.Status.Err()
190 }
191
192 d, err := dynamic.AsDynamicMessage(h.Response)
193 if err != nil {
194 return err
195 }
196
197 items, err := d.TryGetFieldByName("items")
198 if err != nil {
199 return err
200 }
201
202 outputFormat := CharReplacer.Replace(options.Format)
203 if outputFormat == "" {
204 outputFormat = DEFAULT_DEVICE_FORMAT
205 }
206 if options.Quiet {
207 outputFormat = "{{.Id}}"
208 }
209
210 data := make([]model.Device, len(items.([]interface{})))
211 for i, item := range items.([]interface{}) {
212 val := item.(*dynamic.Message)
213 data[i].PopulateFrom(val)
214 }
215
216 result := CommandResult{
217 Format: format.Format(outputFormat),
218 Filter: options.Filter,
219 OrderBy: options.OrderBy,
220 OutputAs: toOutputType(options.OutputAs),
221 NameLimit: options.NameLimit,
222 Data: data,
223 }
224
225 GenerateOutput(&result)
226 return nil
227}
228
229func (options *DeviceCreate) Execute(args []string) error {
230
231 dm := make(map[string]interface{})
232 if options.HostAndPort != "" {
233 dm["host_and_port"] = options.HostAndPort
234 } else if options.IPAddress != "" {
235 dm["ipv4_address"] = options.IPAddress
236 } else if options.MACAddress != "" {
237 dm["mac_address"] = strings.ToLower(options.MACAddress)
238 }
239 if options.DeviceType != "" {
240 dm["type"] = options.DeviceType
241 }
242
243 conn, err := NewConnection()
244 if err != nil {
245 return err
246 }
247 defer conn.Close()
248
249 descriptor, method, err := GetMethod("device-create")
250 if err != nil {
251 return err
252 }
253
254 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
255 defer cancel()
256
257 h := &RpcEventHandler{
258 Fields: map[string]map[string]interface{}{"voltha.Device": dm},
259 }
260 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
261 if err != nil {
262 return err
263 } else if h.Status != nil && h.Status.Err() != nil {
264 return h.Status.Err()
265 }
266
267 resp, err := dynamic.AsDynamicMessage(h.Response)
268 if err != nil {
269 return err
270 }
271 fmt.Printf("%s\n", resp.GetFieldByName("id").(string))
272
273 return nil
274}
275
276func (options *DeviceDelete) Execute(args []string) error {
277
278 conn, err := NewConnection()
279 if err != nil {
280 return err
281 }
282 defer conn.Close()
283
284 descriptor, method, err := GetMethod("device-delete")
285 if err != nil {
286 return err
287 }
288
289 for _, i := range options.Args.Ids {
290
291 h := &RpcEventHandler{
292 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": i}},
293 }
294 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
295 defer cancel()
296
297 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
298 if err != nil {
299 fmt.Printf("Error while deleting '%s': %s\n", i, err)
300 continue
301 } else if h.Status != nil && h.Status.Err() != nil {
302 fmt.Printf("Error while deleting '%s': %s\n", i, h.Status.Err())
303 continue
304 }
305 fmt.Printf("%s\n", i)
306 }
307
308 return nil
309}
310
311func (options *DeviceEnable) Execute(args []string) error {
312 conn, err := NewConnection()
313 if err != nil {
314 return err
315 }
316 defer conn.Close()
317
318 descriptor, method, err := GetMethod("device-enable")
319 if err != nil {
320 return err
321 }
322
323 for _, i := range options.Args.Ids {
324 h := &RpcEventHandler{
325 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": i}},
326 }
327 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
328 defer cancel()
329
330 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
331 if err != nil {
332 fmt.Printf("Error while enabling '%s': %s\n", i, err)
333 continue
334 } else if h.Status != nil && h.Status.Err() != nil {
335 fmt.Printf("Error while enabling '%s': %s\n", i, h.Status.Err())
336 continue
337 }
338 fmt.Printf("%s\n", i)
339 }
340
341 return nil
342}
343
344func (options *DeviceDisable) Execute(args []string) error {
345 conn, err := NewConnection()
346 if err != nil {
347 return err
348 }
349 defer conn.Close()
350
351 descriptor, method, err := GetMethod("device-disable")
352 if err != nil {
353 return err
354 }
355
356 for _, i := range options.Args.Ids {
357 h := &RpcEventHandler{
358 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": i}},
359 }
360 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
361 defer cancel()
362
363 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
364 if err != nil {
365 fmt.Printf("Error while disabling '%s': %s\n", i, err)
366 continue
367 } else if h.Status != nil && h.Status.Err() != nil {
368 fmt.Printf("Error while disabling '%s': %s\n", i, h.Status.Err())
369 continue
370 }
371 fmt.Printf("%s\n", i)
372 }
373
374 return nil
375}
376
377func (options *DeviceReboot) Execute(args []string) error {
378 conn, err := NewConnection()
379 if err != nil {
380 return err
381 }
382 defer conn.Close()
383
384 descriptor, method, err := GetMethod("device-reboot")
385 if err != nil {
386 return err
387 }
388
389 for _, i := range options.Args.Ids {
390 h := &RpcEventHandler{
391 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": i}},
392 }
393 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
394 defer cancel()
395
396 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
397 if err != nil {
398 fmt.Printf("Error while rebooting '%s': %s\n", i, err)
399 continue
400 } else if h.Status != nil && h.Status.Err() != nil {
401 fmt.Printf("Error while rebooting '%s': %s\n", i, h.Status.Err())
402 continue
403 }
404 fmt.Printf("%s\n", i)
405 }
406
407 return nil
408}
409
410func (options *DevicePortList) Execute(args []string) error {
411
412 conn, err := NewConnection()
413 if err != nil {
414 return err
415 }
416 defer conn.Close()
417
418 descriptor, method, err := GetMethod("device-ports")
419 if err != nil {
420 return err
421 }
422
423 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
424 defer cancel()
425
426 h := &RpcEventHandler{
427 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": options.Args.Id}},
428 }
429 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
430 if err != nil {
431 return err
432 }
433
434 if h.Status != nil && h.Status.Err() != nil {
435 return h.Status.Err()
436 }
437
438 d, err := dynamic.AsDynamicMessage(h.Response)
439 if err != nil {
440 return err
441 }
442
443 items, err := d.TryGetFieldByName("items")
444 if err != nil {
445 return err
446 }
447
448 outputFormat := CharReplacer.Replace(options.Format)
449 if outputFormat == "" {
450 outputFormat = DEFAULT_DEVICE_PORTS_FORMAT
451 }
452 if options.Quiet {
453 outputFormat = "{{.Id}}"
454 }
455
456 data := make([]model.DevicePort, len(items.([]interface{})))
457 for i, item := range items.([]interface{}) {
458 data[i].PopulateFrom(item.(*dynamic.Message))
459 }
460
461 result := CommandResult{
462 Format: format.Format(outputFormat),
463 Filter: options.Filter,
464 OrderBy: options.OrderBy,
465 OutputAs: toOutputType(options.OutputAs),
466 NameLimit: options.NameLimit,
467 Data: data,
468 }
469
470 GenerateOutput(&result)
471 return nil
472}
473
474func (options *DeviceFlowList) Execute(args []string) error {
475 fl := &FlowList{}
476 fl.ListOutputOptions = options.ListOutputOptions
477 fl.Args.Id = string(options.Args.Id)
478 fl.Method = "device-flow-list"
479 return fl.Execute(args)
480}
481
482func (options *DeviceInspect) Execute(args []string) error {
483 if len(args) > 0 {
484 return fmt.Errorf("only a single argument 'DEVICE_ID' can be provided")
485 }
486
487 conn, err := NewConnection()
488 if err != nil {
489 return err
490 }
491 defer conn.Close()
492
493 descriptor, method, err := GetMethod("device-inspect")
494 if err != nil {
495 return err
496 }
497
498 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
499 defer cancel()
500
501 h := &RpcEventHandler{
502 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": options.Args.Id}},
503 }
504 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{"Get-Depth: 2"}, h, h.GetParams)
505 if err != nil {
506 return err
507 } else if h.Status != nil && h.Status.Err() != nil {
508 return h.Status.Err()
509 }
510
511 d, err := dynamic.AsDynamicMessage(h.Response)
512 if err != nil {
513 return err
514 }
515
516 device := &model.Device{}
517 device.PopulateFrom(d)
518
519 outputFormat := CharReplacer.Replace(options.Format)
520 if outputFormat == "" {
521 outputFormat = DEFAULT_DEVICE_INSPECT_FORMAT
522 }
523 if options.Quiet {
524 outputFormat = "{{.Id}}"
525 }
526
527 result := CommandResult{
528 Format: format.Format(outputFormat),
529 OutputAs: toOutputType(options.OutputAs),
530 NameLimit: options.NameLimit,
531 Data: device,
532 }
533 GenerateOutput(&result)
534 return nil
535}