Skip to content

Commit 97c8f08

Browse files
authored
Merge pull request #3 from digitalocean/mdl-ovsnl-client
ovsnl: initial commit of Client
2 parents a233bc4 + 5d3b8c8 commit 97c8f08

File tree

3 files changed

+275
-0
lines changed

3 files changed

+275
-0
lines changed

ovsnl/client.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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 ovsnl
16+
17+
import (
18+
"fmt"
19+
"os"
20+
"strings"
21+
22+
"github.com/digitalocean/go-openvswitch/ovsnl/internal/ovsh"
23+
"github.com/mdlayher/genetlink"
24+
)
25+
26+
// A Client is a Linux Open vSwitch generic netlink client.
27+
type Client struct {
28+
c *genetlink.Conn
29+
}
30+
31+
// New creates a new Linux Open vSwitch generic netlink client.
32+
//
33+
// If no OvS generic netlink families are available on this system, an
34+
// error will be returned which can be checked using os.IsNotExist.
35+
func New() (*Client, error) {
36+
c, err := genetlink.Dial(nil)
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
return newClient(c)
42+
}
43+
44+
// newClient is the internal Client constructor, used in tests.
45+
func newClient(c *genetlink.Conn) (*Client, error) {
46+
// Must ensure that the generic netlink connection is closed on any errors
47+
// that occur before it is returned to the caller.
48+
49+
families, err := c.ListFamilies()
50+
if err != nil {
51+
_ = c.Close()
52+
return nil, err
53+
}
54+
55+
client := &Client{c: c}
56+
if err := client.init(families); err != nil {
57+
_ = c.Close()
58+
return nil, err
59+
}
60+
61+
return client, nil
62+
}
63+
64+
// Close closes the Client's generic netlink connection.
65+
func (c *Client) Close() error {
66+
return c.c.Close()
67+
}
68+
69+
// init initializes the generic netlink family service of Client.
70+
func (c *Client) init(families []genetlink.Family) error {
71+
// Assume 4 families present.
72+
var gotf int
73+
const wantf = 4
74+
75+
for _, f := range families {
76+
// Ignore any families without the OVS prefix.
77+
if !strings.HasPrefix(f.Name, "ovs_") {
78+
continue
79+
}
80+
81+
gotf++
82+
if err := c.initFamily(f); err != nil {
83+
return err
84+
}
85+
}
86+
87+
// No families; return error for os.IsNotExist check.
88+
if gotf == 0 {
89+
return os.ErrNotExist
90+
}
91+
92+
if gotf != wantf {
93+
return fmt.Errorf("expected %d OVS generic netlink families, but found %d",
94+
wantf, gotf)
95+
}
96+
97+
return nil
98+
}
99+
100+
// initFamily initializes a single generic netlink family service.
101+
func (c *Client) initFamily(f genetlink.Family) error {
102+
switch f.Name {
103+
case ovsh.DatapathFamily, ovsh.FlowFamily, ovsh.PacketFamily, ovsh.VportFamily:
104+
// TODO(mdlayher): populate.
105+
return nil
106+
}
107+
108+
return fmt.Errorf("unrecognized OVS generic netlink family: %q", f.Name)
109+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
//+build linux
16+
17+
package ovsnl_test
18+
19+
import (
20+
"os"
21+
"testing"
22+
23+
"github.com/digitalocean/go-openvswitch/ovsnl"
24+
)
25+
26+
func TestLinuxClientIntegration(t *testing.T) {
27+
c, err := ovsnl.New()
28+
if err != nil {
29+
if os.IsNotExist(err) {
30+
t.Skipf("generic netlink OVS families not found: %v", err)
31+
}
32+
33+
t.Fatalf("failed to create client %v", err)
34+
}
35+
defer c.Close()
36+
37+
// TODO(mdlayher): fill in after Client has more methods.
38+
_ = c
39+
}

ovsnl/client_linux_test.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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+
//+build linux
16+
17+
package ovsnl
18+
19+
import (
20+
"fmt"
21+
"os"
22+
"testing"
23+
24+
"github.com/digitalocean/go-openvswitch/ovsnl/internal/ovsh"
25+
"github.com/mdlayher/genetlink"
26+
"github.com/mdlayher/genetlink/genltest"
27+
"github.com/mdlayher/netlink"
28+
"github.com/mdlayher/netlink/nlenc"
29+
"golang.org/x/sys/unix"
30+
)
31+
32+
func TestClientNoFamiliesIsNotExist(t *testing.T) {
33+
conn := genltest.Dial(func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) {
34+
// Unrelated generic netlink families.
35+
return familyMessages([]string{
36+
"TASKSTATS",
37+
"nl80211",
38+
}), nil
39+
})
40+
41+
_, err := newClient(conn)
42+
if !os.IsNotExist(err) {
43+
t.Fatalf("expected is not exist error, but got: %v", err)
44+
}
45+
46+
t.Logf("OK error: %v", err)
47+
}
48+
49+
func TestClientInvalidFamily(t *testing.T) {
50+
conn := genltest.Dial(func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) {
51+
return familyMessages([]string{
52+
"ovs_foo",
53+
}), nil
54+
})
55+
56+
_, err := newClient(conn)
57+
if err == nil {
58+
t.Fatalf("expected an error, but none occurred")
59+
}
60+
61+
t.Logf("OK error: %v", err)
62+
}
63+
64+
func TestClientMissingFamilies(t *testing.T) {
65+
conn := genltest.Dial(func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) {
66+
// Too few OVS families.
67+
return familyMessages([]string{
68+
ovsh.DatapathFamily,
69+
}), nil
70+
})
71+
72+
_, err := newClient(conn)
73+
if err == nil {
74+
t.Fatalf("expected an error, but none occurred")
75+
}
76+
77+
t.Logf("OK error: %v", err)
78+
}
79+
80+
func TestClientOK(t *testing.T) {
81+
conn := genltest.Dial(func(greq genetlink.Message, nreq netlink.Message) ([]genetlink.Message, error) {
82+
return familyMessages([]string{
83+
ovsh.DatapathFamily,
84+
ovsh.FlowFamily,
85+
ovsh.PacketFamily,
86+
ovsh.VportFamily,
87+
}), nil
88+
})
89+
90+
_, err := newClient(conn)
91+
if err != nil {
92+
t.Fatalf("failed to create client: %v", err)
93+
}
94+
}
95+
96+
func familyMessages(families []string) []genetlink.Message {
97+
msgs := make([]genetlink.Message, 0, len(families))
98+
99+
var id uint16
100+
for _, f := range families {
101+
msgs = append(msgs, genetlink.Message{
102+
Data: mustMarshalAttributes([]netlink.Attribute{
103+
{
104+
Type: unix.CTRL_ATTR_FAMILY_ID,
105+
Data: nlenc.Uint16Bytes(id),
106+
},
107+
{
108+
Type: unix.CTRL_ATTR_FAMILY_NAME,
109+
Data: nlenc.Bytes(f),
110+
},
111+
}),
112+
})
113+
114+
id++
115+
}
116+
117+
return msgs
118+
}
119+
120+
func mustMarshalAttributes(attrs []netlink.Attribute) []byte {
121+
b, err := netlink.MarshalAttributes(attrs)
122+
if err != nil {
123+
panic(fmt.Sprintf("failed to marshal attributes: %v", err))
124+
}
125+
126+
return b
127+
}

0 commit comments

Comments
 (0)