Skip to content

Commit 5b59e08

Browse files
author
Franck Rupin
committed
Add wrappers for ovs-dpctl cli
- This commit adds wrappers for unimplemented ovs-dpctl commands. - These wrappers will allow to setup ovs conntrack limits - It adds wrappers to the following ovs-dpctl commands: - --version - dump-dps - add-dp - del-dp - ct-get-limits - ct-set-limits - ct-del-limits
1 parent e0e0c78 commit 5b59e08

File tree

1 file changed

+335
-0
lines changed

1 file changed

+335
-0
lines changed

ovs/datapath.go

Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
// Copyright 2017 DigitalOcean.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ovs
16+
17+
import (
18+
"errors"
19+
"fmt"
20+
"regexp"
21+
"strconv"
22+
"strings"
23+
)
24+
25+
// Zone defines the type used to store a zone as it is returned
26+
// by ovs-dpctl ct-*-limits commands
27+
type Zone map[string]uint64
28+
29+
// ConnTrackOutput is a type defined to store the output
30+
// of ovs-dpctl ct-*-limits commands. For example it stores
31+
// such a cli output:
32+
// # ovs-dpctl ct-get-limits system@ovs-system zone=2,3
33+
// default limit=0
34+
// zone=2,limit=0,count=0
35+
// zone=3,limit=0,count=0
36+
type ConnTrackOutput struct {
37+
// defaulCT is used to store the global setting: default
38+
defaultLimit Zone
39+
//zones stores all remaning zone's settings
40+
zones []Zone
41+
}
42+
43+
// DataPathReader is the interface defining the read operations
44+
// for the ovs DataPaths
45+
type DataPathReader interface {
46+
// Version is the method used to get the version of ovs-dpctl
47+
Version() (string, error)
48+
// GetDataPath is the method that returns all DataPaths setup
49+
// for an ovs switch
50+
GetDataPath() ([]string, error)
51+
}
52+
53+
// DataPathWriter is the interface defining the wrtie operations
54+
// for the ovs DataPaths
55+
type DataPathWriter interface {
56+
// AddDataPath is the method used to add a datapath to the switch
57+
AddDataPath(string) ([]byte, error)
58+
// DelDataPath is the method used to remove a datapath from the switch
59+
DelDataPath(string) ([]string, error)
60+
}
61+
62+
// ConnTrackReader is the interface defining the read operations
63+
// of ovs conntrack
64+
type ConnTrackReader interface {
65+
// GetCTLimits is the method used to querying conntrack limits for a
66+
// datapath on a switch
67+
GetCTLimits(string, []uint64) (ConnTrackOutput, error)
68+
}
69+
70+
// ConnTrackWriter is the interface defining the write operations
71+
// of ovs conntrack
72+
type ConnTrackWriter interface {
73+
// SetCTLimits is the method used to setup a limit for an ofport (zone)
74+
// belonging to a datapath of a switch
75+
SetCTLimits(string) (string, error)
76+
// DelCTLimits is the method used to remove a limit to an ofport (zone)
77+
// belonging to a datapath of a switch
78+
DelCTLimits(string, []uint64) (string, error)
79+
}
80+
81+
// CLI is an interface defining a contract for executing a command.
82+
// Implementation of shell cli is done by the Client concrete type
83+
type CLI interface {
84+
Exec(args ...string) ([]byte, error)
85+
}
86+
87+
// DataPathService defines the conrete type used for DataPath operations
88+
// supported by the ovs-dpctl command
89+
type DataPathService struct {
90+
// We define here a CLI interface making easier to mock pvs-dpctl command
91+
// as in github.com/digitalocean/go-openvswitch/ovs/datapath_test.go
92+
CLI
93+
}
94+
95+
// NewDataPathService is a builder for the DataPathService.
96+
// sudo is defined as a default option.
97+
func NewDataPathService() *DataPathService {
98+
return &DataPathService{
99+
CLI: &OvsCLI{
100+
c: New(Sudo()),
101+
},
102+
}
103+
}
104+
105+
// Version retruns the ovs-dptcl --version currently installed
106+
func (dp *DataPathService) Version() (string, error) {
107+
args := []string{"--version"}
108+
result, err := dp.CLI.Exec(args...)
109+
if err != nil {
110+
return "", err
111+
}
112+
113+
return string(result), nil
114+
}
115+
116+
// GetDataPaths returns the output of the command 'ovs-dpctl dump-dps'
117+
func (dp *DataPathService) GetDataPaths() ([]string, error) {
118+
args := []string{"dump-dps"}
119+
result, err := dp.CLI.Exec(args...)
120+
if err != nil {
121+
return nil, err
122+
}
123+
124+
return strings.Split(string(result), "\n"), nil
125+
}
126+
127+
// AddDataPath create a Datapath with the command 'ovs-dpctl add-dp <DP>'
128+
// It takes one argument, the required DataPath Name and returns an error
129+
// if it failed
130+
func (dp *DataPathService) AddDataPath(dpName string) ([]byte, error) {
131+
args := []string{"add-dp", dpName}
132+
133+
return dp.CLI.Exec(args...)
134+
}
135+
136+
// DelDataPath create a Datapath with the command 'ovs-dpctl add-dp <DP>'
137+
// It takes one argument, the required DataPath Name and returns an error
138+
// if it failed
139+
func (dp *DataPathService) DelDataPath(dpName string) ([]byte, error) {
140+
args := []string{"del-dp", dpName}
141+
142+
return dp.CLI.Exec(args...)
143+
}
144+
145+
// GetCTLimits returns the conntrack limits for a given datapath
146+
// equivalent to running: 'sudo ovs-dpctl ct-get-limits <datapath_name> zone=<#1>,<#2>,...'
147+
func (dp *DataPathService) GetCTLimits(dpName string, zones []uint64) (*ConnTrackOutput, error) {
148+
// Start by building the args
149+
args := []string{"ct-get-limits"}
150+
if dpName == "" {
151+
return nil, errors.New("datapath name argument is mandatory")
152+
}
153+
154+
args = append(args, dpName)
155+
156+
zoneParam := getZoneString(zones)
157+
if zoneParam != "" {
158+
args = append(args, zoneParam)
159+
}
160+
161+
// call the cli
162+
results, err := dp.CLI.Exec(args...)
163+
if err != nil {
164+
return nil, err
165+
}
166+
167+
// Process the results
168+
entries := strings.Split(string(results), "\n")
169+
ctOut := &ConnTrackOutput{}
170+
171+
r, err := regexp.Compile(`default`)
172+
if err != nil {
173+
fmt.Println(err.Error())
174+
return nil, err
175+
}
176+
177+
// First start extracting the default conntrack limit setup
178+
// If found the default value is removed from the entries
179+
for i, entry := range entries {
180+
if r.MatchString(entry) {
181+
ctOut.defaultLimit = make(Zone)
182+
limit, err := strconv.Atoi(strings.Split(entry, "=")[1])
183+
if err != nil {
184+
return nil, err
185+
}
186+
ctOut.defaultLimit["default"] = uint64(limit)
187+
// As the default has been found let's remove it
188+
entries = append(entries[:i], entries[i+1:]...)
189+
}
190+
}
191+
192+
// Now process the zones setup
193+
for _, entry := range entries {
194+
fields := strings.Split(entry, ",")
195+
z := make(Zone)
196+
for _, field := range fields {
197+
buf := strings.Split(field, "=")
198+
val, _ := strconv.Atoi(buf[1])
199+
z[buf[0]] = uint64(val)
200+
}
201+
ctOut.zones = append(ctOut.zones, z)
202+
}
203+
204+
return ctOut, nil
205+
}
206+
207+
// SetCTLimits set the limit for a specific zone or globally.
208+
// Only one zone or default can be set up at once as the cli allows.
209+
// Examples of commands it wrapps:
210+
// sudo ovs-dpctl ct-set-limits system@ovs-system zone=331,limit=1000000
211+
// sudo ovs-dpctl ct-set-limits system@ovs-system default=1000000
212+
func (dp *DataPathService) SetCTLimits(dpName string, zone map[string]uint64) (string, error) {
213+
// Sanitize the input
214+
if dpName == "" {
215+
return "", errors.New("datapath name is required")
216+
}
217+
argsStr, err := ctSetLimitsArgsToString(zone)
218+
if err != nil {
219+
return "", err
220+
}
221+
// call the cli
222+
argsCLI := []string{"ct-set-limits", dpName, argsStr}
223+
results, err := dp.CLI.Exec(argsCLI...)
224+
225+
return string(results), err
226+
}
227+
228+
// DelCTLimits deletes limits setup for zones. It takes the Datapath name
229+
// and zones to delete the limits.
230+
// sudo ovs-dpctl ct-del-limits system@ovs-system zone=40,4
231+
func (dp *DataPathService) DelCTLimits(dpName string, zones []uint64) (string, error) {
232+
if dpName == "" {
233+
return "", errors.New("datapath name is missing")
234+
}
235+
if len(zones) < 1 {
236+
return "", errors.New("at least 1 zone is mandatory")
237+
}
238+
239+
var firstZone uint64
240+
firstZone, zones = zones[0], zones[1:]
241+
zonesStr := "zone=" + strconv.FormatUint(firstZone, 10)
242+
for _, z := range zones {
243+
zonesStr += "," + strconv.FormatUint(z, 10)
244+
}
245+
246+
// call the cli
247+
argsCLI := []string{"ct-del-limits", dpName, zonesStr}
248+
results, err := dp.CLI.Exec(argsCLI...)
249+
250+
return string(results), err
251+
}
252+
253+
// ctSetLimitsArgsToString is function to help formating and sanatizing an input
254+
// It takes a map and output a string like this:
255+
// - "zone=2,limit=10000" or "limit=10000,zone=2"
256+
// - "default=10000"
257+
func ctSetLimitsArgsToString(zone map[string]uint64) (string, error) {
258+
defaultSetup := false
259+
args := make([]string, 0)
260+
for k, v := range zone {
261+
if k == "default" {
262+
args = append(args, k+"="+strconv.FormatUint(v, 10))
263+
defaultSetup = true
264+
} else if k == "zone" || k == "limit" {
265+
args = append(args, k+"="+strconv.FormatUint(v, 10))
266+
}
267+
}
268+
269+
// We need at most 2 arguments and at least 1
270+
if len(args) == 0 || len(args) > 2 {
271+
return "", errors.New("missing or too much arguments to setup ct limits")
272+
273+
}
274+
// if we setup the default global setting we only need a single parameter
275+
// like "default=100000" and nothing else
276+
if defaultSetup && len(args) != 1 {
277+
return "", errors.New("wrong argument while setting default ct limits")
278+
}
279+
// if we setup a limit for dedicated zone we need 2 params like
280+
// "zone=3" and "limit=50000"
281+
if !defaultSetup && len(args) != 2 {
282+
return "", errors.New("wrong argument while setting zone ct limits")
283+
}
284+
285+
var argsStr string
286+
argsStr, args = args[0], args[1:]
287+
if len(args) > 0 {
288+
for _, s := range args {
289+
argsStr += "," + s
290+
}
291+
}
292+
return argsStr, nil
293+
}
294+
295+
// getZoneString takes the zones as []uint64 to return a formated
296+
// string usable in different ovs-dpctl commands
297+
// Example a slice: var zones = []uint64{2, 3, 4}
298+
// will output: "zone=2,3,4"
299+
func getZoneString(z []uint64) string {
300+
zonesStr := make([]string, 0)
301+
for _, zone := range z {
302+
zonesStr = append(zonesStr, strconv.FormatUint(zone, 10))
303+
}
304+
305+
var sb strings.Builder
306+
var firstZone string
307+
if len(zonesStr) > 0 {
308+
sb.WriteString("zone=")
309+
firstZone, zonesStr = zonesStr[0], zonesStr[1:]
310+
}
311+
sb.WriteString(firstZone)
312+
313+
for _, zone := range zonesStr {
314+
sb.WriteString(",")
315+
sb.WriteString(zone)
316+
}
317+
318+
return sb.String()
319+
}
320+
321+
// OvsCLI implements the CLI interface by invoking the Client exec
322+
// method.
323+
type OvsCLI struct {
324+
// Wrapped client for ovs-dpctl
325+
c *Client
326+
}
327+
328+
// Exec executes 'ovs-dpctl' + args passed in argument
329+
func (cli *OvsCLI) Exec(args ...string) ([]byte, error) {
330+
if cli.c == nil {
331+
return nil, errors.New("client unitialized")
332+
}
333+
334+
return cli.c.exec("ovs-dpctl", args...)
335+
}

0 commit comments

Comments
 (0)