Skip to content

Commit 442ae6c

Browse files
author
Franck Rupin
authored
Merge pull request #100 from franckrupin/franckrupin/datapath-wrapper
Franckrupin/datapath wrapper
2 parents 189ec6a + 32dd526 commit 442ae6c

File tree

6 files changed

+1015
-4
lines changed

6 files changed

+1015
-4
lines changed

.github/workflows/go.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
echo "=========START LINT============"
5353
golint -set_exit_status ./cmd/... ./internal/...
5454
echo "=========START TESTS IN OVS============"
55-
go test -v -race ./ovs/
55+
go test -v -race -short ./ovs/
5656
echo "=========START TESTS IN OVSDB============"
5757
go test -v -race ./ovsdb/
5858
go test -c -race ./ovsdb

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ Tejas Kokje <[email protected]>
1313
Kei Nohguchi <[email protected]>
1414
Neal Shrader <[email protected]>
1515
Sangeetha Srikanth <[email protected]>
16+
Franck Rupin <[email protected]>

ovs/client.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ type Client struct {
3636
// VSwitch wraps functionality of the 'ovs-vsctl' binary.
3737
VSwitch *VSwitchService
3838

39+
// DataPath wraps functionality of the 'ovs-dpctl' binary
40+
DataPath *DataPathService
41+
3942
// Additional flags applied to all OVS actions, such as timeouts
4043
// or retries.
4144
flags []string
@@ -240,6 +243,12 @@ func New(options ...OptionFunc) *Client {
240243
}
241244
c.App = app
242245

246+
c.DataPath = &DataPathService{
247+
CLI: &DpCLI{
248+
c: c,
249+
},
250+
}
251+
243252
return c
244253
}
245254

ovs/datapath.go

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

0 commit comments

Comments
 (0)