blob: ccdfc050d97b921f3ba7ea6c1f95b05957bc0d61 [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,
69 "ClearActions": 1020,
70 }
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
105func toVlanId(vid uint32) string {
106 if vid == 0 {
107 return "untagged"
108 } else if vid&0x1000 > 0 {
109 return fmt.Sprintf("%d", vid-4096)
110 }
111 return fmt.Sprintf("%d", vid)
112}
113
114func appendInt32(base string, val int32) string {
115 if len(base) > 0 {
116 return fmt.Sprintf("%s,%d", base, val)
117 }
118 return fmt.Sprintf("%d", val)
119}
120
121func appendUint32(base string, val uint32) string {
122 if len(base) > 0 {
123 return fmt.Sprintf("%s,%d", base, val)
124 }
125 return fmt.Sprintf("%d", val)
126}
127
128func (options *FlowList) Execute(args []string) error {
129 if len(args) > 0 {
130 return fmt.Errorf("only a single argument 'DEVICE_ID' can be provided")
131 }
132
133 conn, err := NewConnection()
134 if err != nil {
135 return err
136 }
137 defer conn.Close()
138
139 switch options.Method {
140 case "device-flow-list":
141 case "logical-device-flow-list":
142 default:
143 panic(fmt.Errorf("Unknown method name: '%s'", options.Method))
144 }
145
146 descriptor, method, err := GetMethod(options.Method)
147 if err != nil {
148 return err
149 }
150
151 ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
152 defer cancel()
153
154 h := &RpcEventHandler{
155 Fields: map[string]map[string]interface{}{ParamNames[GlobalConfig.ApiVersion]["ID"]: {"id": options.Args.Id}},
156 }
157 err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, []string{}, h, h.GetParams)
158 if err != nil {
159 return err
160 } else if h.Status != nil && h.Status.Err() != nil {
161 return h.Status.Err()
162 }
163
164 d, err := dynamic.AsDynamicMessage(h.Response)
165 if err != nil {
166 return err
167 }
168 items, err := d.TryGetFieldByName("items")
169 if err != nil {
170 return err
171 }
172
173 if toOutputType(options.OutputAs) == OUTPUT_TABLE && (items == nil || len(items.([]interface{})) == 0) {
174 fmt.Println("*** NO FLOWS ***")
175 return nil
176 }
177
178 // Walk the flows and populate the output table
179 data := make([]model.Flow, len(items.([]interface{})))
180 var fieldset model.FlowFieldFlag
181 for i, item := range items.([]interface{}) {
182 val := item.(*dynamic.Message)
183 data[i].PopulateFrom(val)
184 fieldset |= data[i].Populated()
185 }
186
187 outputFormat := CharReplacer.Replace(options.Format)
188 if options.Quiet {
189 outputFormat = "{{.Id}}"
190 } else if outputFormat == "" {
191 outputFormat = buildOutputFormat(fieldset, model.FLOW_FIELD_STATS)
192 }
193
194 result := CommandResult{
195 Format: format.Format(outputFormat),
196 Filter: options.Filter,
197 OrderBy: options.OrderBy,
198 OutputAs: toOutputType(options.OutputAs),
199 NameLimit: options.NameLimit,
200 Data: data,
201 }
202 GenerateOutput(&result)
203
204 return nil
205}