blob: acbb09ced47e71fdd9428468a859fb638907ac22 [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 (
43 flowOpts = FlowOpts{}
44
45 // Used to sort the table colums in a consistent order
46 SORT_ORDER = map[string]uint16{
47 "Id": 0,
48 "TableId": 10,
49 "Priority": 20,
50 "Cookie": 30,
51 "UnsupportedMatch": 35,
52 "InPort": 40,
53 "VlanId": 50,
54 "VlanPcp": 55,
55 "EthType": 60,
56 "IpProto": 70,
57 "UdpSrc": 80,
58 "UdpDst": 90,
59 "Metadata": 100,
60 "TunnelId": 101,
61 "UnsupportedInstruction": 102,
62 "UnsupportedAction": 105,
63 "UnsupportedSetField": 107,
64 "SetVlanId": 110,
65 "PopVlan": 120,
66 "PushVlanId": 130,
67 "Output": 1000,
68 "GotoTable": 1010,
David Bainbridge09c61672019-08-23 19:07:54 +000069 "WriteMetadata": 1015,
Zack Williamse940c7a2019-08-21 14:25:39 -070070 "ClearActions": 1020,
David Bainbridge09c61672019-08-23 19:07:54 +000071 "MeterId": 1030,
Zack Williamse940c7a2019-08-21 14:25:39 -070072 }
73)
74
75/*
76 * Construct a template format string based on the fields required by the
77 * results.
78 */
79func buildOutputFormat(fieldset model.FlowFieldFlag, ignore model.FlowFieldFlag) string {
80 want := fieldset & ^(ignore)
81 fields := make([]string, want.Count())
82 idx := 0
83 for _, flag := range model.AllFlowFieldFlags {
84 if want.IsSet(flag) {
85 fields[idx] = flag.String()
86 idx += 1
87 }
88 }
89 sort.Slice(fields, func(i, j int) bool {
90 return SORT_ORDER[fields[i]] < SORT_ORDER[fields[j]]
91 })
92 var b strings.Builder
93 b.WriteString("table")
94 first := true
95 for _, k := range fields {
96 if !first {
97 b.WriteString("\t")
98 }
99 first = false
100 b.WriteString("{{.")
101 b.WriteString(k)
102 b.WriteString("}}")
103 }
104 return b.String()
105}
106
107func toVlanId(vid uint32) string {
108 if vid == 0 {
109 return "untagged"
110 } else if vid&0x1000 > 0 {
111 return fmt.Sprintf("%d", vid-4096)
112 }
113 return fmt.Sprintf("%d", vid)
114}
115
116func appendInt32(base string, val int32) string {
117 if len(base) > 0 {
118 return fmt.Sprintf("%s,%d", base, val)
119 }
120 return fmt.Sprintf("%d", val)
121}
122
123func appendUint32(base string, val uint32) string {
124 if len(base) > 0 {
125 return fmt.Sprintf("%s,%d", base, val)
126 }
127 return fmt.Sprintf("%d", val)
128}
129
130func (options *FlowList) Execute(args []string) error {
131 if len(args) > 0 {
132 return fmt.Errorf("only a single argument 'DEVICE_ID' can be provided")
133 }
134
135 conn, err := NewConnection()
136 if err != nil {
137 return err
138 }
139 defer conn.Close()
140
141 switch options.Method {
142 case "device-flow-list":
143 case "logical-device-flow-list":
144 default:
145 panic(fmt.Errorf("Unknown method name: '%s'", options.Method))
146 }
147
148 descriptor, method, err := GetMethod(options.Method)
149 if err != nil {
150 return err
151 }
152
153 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
154 defer cancel()
155
156 h := &RpcEventHandler{
157 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": options.Args.Id}},
158 }
159 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
160 if err != nil {
161 return err
162 } else if h.Status != nil && h.Status.Err() != nil {
163 return h.Status.Err()
164 }
165
166 d, err := dynamic.AsDynamicMessage(h.Response)
167 if err != nil {
168 return err
169 }
170 items, err := d.TryGetFieldByName("items")
171 if err != nil {
172 return err
173 }
174
175 if toOutputType(options.OutputAs) == OUTPUT_TABLE && (items == nil || len(items.([]interface{})) == 0) {
176 fmt.Println("*** NO FLOWS ***")
177 return nil
178 }
179
180 // Walk the flows and populate the output table
181 data := make([]model.Flow, len(items.([]interface{})))
182 var fieldset model.FlowFieldFlag
183 for i, item := range items.([]interface{}) {
184 val := item.(*dynamic.Message)
185 data[i].PopulateFrom(val)
186 fieldset |= data[i].Populated()
187 }
188
189 outputFormat := CharReplacer.Replace(options.Format)
190 if options.Quiet {
191 outputFormat = "{{.Id}}"
192 } else if outputFormat == "" {
193 outputFormat = buildOutputFormat(fieldset, model.FLOW_FIELD_STATS)
194 }
195
196 result := CommandResult{
197 Format: format.Format(outputFormat),
198 Filter: options.Filter,
199 OrderBy: options.OrderBy,
200 OutputAs: toOutputType(options.OutputAs),
201 NameLimit: options.NameLimit,
202 Data: data,
203 }
204 GenerateOutput(&result)
205
206 return nil
207}