blob: b8c2eb6689ac548c62d1b52ca6591b11f4d846e9 [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 "github.com/jhump/protoreflect/dynamic"
Scott Baker2b0ad652019-08-21 14:57:07 -070023 "github.com/opencord/voltctl/pkg/format"
24 "github.com/opencord/voltctl/pkg/model"
Zack Williamse940c7a2019-08-21 14:25:39 -070025 "sort"
26 "strings"
27)
28
29type FlowList struct {
30 ListOutputOptions
31 Args struct {
32 Id string `positional-arg-name:"DEVICE_ID" required:"yes"`
33 } `positional-args:"yes"`
34
35 Method string
36}
37
38type FlowOpts struct {
39 List FlowList `command:"list"`
40}
41
42var (
Zack Williamse940c7a2019-08-21 14:25:39 -070043 // Used to sort the table colums in a consistent order
44 SORT_ORDER = map[string]uint16{
45 "Id": 0,
46 "TableId": 10,
47 "Priority": 20,
48 "Cookie": 30,
49 "UnsupportedMatch": 35,
50 "InPort": 40,
51 "VlanId": 50,
52 "VlanPcp": 55,
53 "EthType": 60,
54 "IpProto": 70,
55 "UdpSrc": 80,
56 "UdpDst": 90,
57 "Metadata": 100,
58 "TunnelId": 101,
59 "UnsupportedInstruction": 102,
60 "UnsupportedAction": 105,
61 "UnsupportedSetField": 107,
62 "SetVlanId": 110,
63 "PopVlan": 120,
64 "PushVlanId": 130,
65 "Output": 1000,
66 "GotoTable": 1010,
David Bainbridge09c61672019-08-23 19:07:54 +000067 "WriteMetadata": 1015,
Zack Williamse940c7a2019-08-21 14:25:39 -070068 "ClearActions": 1020,
David Bainbridge09c61672019-08-23 19:07:54 +000069 "MeterId": 1030,
Zack Williamse940c7a2019-08-21 14:25:39 -070070 }
71)
72
73/*
74 * Construct a template format string based on the fields required by the
75 * results.
76 */
77func buildOutputFormat(fieldset model.FlowFieldFlag, ignore model.FlowFieldFlag) string {
78 want := fieldset & ^(ignore)
79 fields := make([]string, want.Count())
80 idx := 0
81 for _, flag := range model.AllFlowFieldFlags {
82 if want.IsSet(flag) {
83 fields[idx] = flag.String()
84 idx += 1
85 }
86 }
87 sort.Slice(fields, func(i, j int) bool {
88 return SORT_ORDER[fields[i]] < SORT_ORDER[fields[j]]
89 })
90 var b strings.Builder
91 b.WriteString("table")
92 first := true
93 for _, k := range fields {
94 if !first {
95 b.WriteString("\t")
96 }
97 first = false
98 b.WriteString("{{.")
99 b.WriteString(k)
100 b.WriteString("}}")
101 }
102 return b.String()
103}
104
Zack Williamse940c7a2019-08-21 14:25:39 -0700105func (options *FlowList) Execute(args []string) error {
106 if len(args) > 0 {
107 return fmt.Errorf("only a single argument 'DEVICE_ID' can be provided")
108 }
109
110 conn, err := NewConnection()
111 if err != nil {
112 return err
113 }
114 defer conn.Close()
115
116 switch options.Method {
David Bainbridgea6722342019-10-24 23:55:53 +0000117 case "device-flows":
118 case "logical-device-flows":
Zack Williamse940c7a2019-08-21 14:25:39 -0700119 default:
David Bainbridge0f758d42019-10-26 05:17:48 +0000120 Error.Fatalf("Unknown method name: '%s'", options.Method)
Zack Williamse940c7a2019-08-21 14:25:39 -0700121 }
122
123 descriptor, method, err := GetMethod(options.Method)
124 if err != nil {
125 return err
126 }
127
128 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
129 defer cancel()
130
131 h := &RpcEventHandler{
132 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": options.Args.Id}},
133 }
134 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
135 if err != nil {
136 return err
137 } else if h.Status != nil && h.Status.Err() != nil {
138 return h.Status.Err()
139 }
140
141 d, err := dynamic.AsDynamicMessage(h.Response)
142 if err != nil {
143 return err
144 }
145 items, err := d.TryGetFieldByName("items")
146 if err != nil {
147 return err
148 }
149
150 if toOutputType(options.OutputAs) == OUTPUT_TABLE && (items == nil || len(items.([]interface{})) == 0) {
151 fmt.Println("*** NO FLOWS ***")
152 return nil
153 }
154
155 // Walk the flows and populate the output table
156 data := make([]model.Flow, len(items.([]interface{})))
157 var fieldset model.FlowFieldFlag
158 for i, item := range items.([]interface{}) {
159 val := item.(*dynamic.Message)
160 data[i].PopulateFrom(val)
161 fieldset |= data[i].Populated()
162 }
163
164 outputFormat := CharReplacer.Replace(options.Format)
165 if options.Quiet {
166 outputFormat = "{{.Id}}"
167 } else if outputFormat == "" {
David Bainbridgea6722342019-10-24 23:55:53 +0000168 outputFormat = GetCommandOptionWithDefault(options.Method, "format", buildOutputFormat(fieldset, model.FLOW_FIELD_STATS))
169 }
170
171 orderBy := options.OrderBy
172 if orderBy == "" {
173 orderBy = GetCommandOptionWithDefault(options.Method, "order", "")
Zack Williamse940c7a2019-08-21 14:25:39 -0700174 }
175
176 result := CommandResult{
177 Format: format.Format(outputFormat),
178 Filter: options.Filter,
David Bainbridgea6722342019-10-24 23:55:53 +0000179 OrderBy: orderBy,
Zack Williamse940c7a2019-08-21 14:25:39 -0700180 OutputAs: toOutputType(options.OutputAs),
181 NameLimit: options.NameLimit,
182 Data: data,
183 }
184 GenerateOutput(&result)
185
186 return nil
187}