Skip to content

Commit

Permalink
Add implementation for OVSNetwork controller
Browse files Browse the repository at this point in the history
Signed-off-by: Yury Kulazhenkov <[email protected]>
  • Loading branch information
ykulazhenkov committed Mar 18, 2024
1 parent a51d01b commit de0caa8
Show file tree
Hide file tree
Showing 12 changed files with 511 additions and 27 deletions.
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ The SR-IOV network operator introduces following new CRDs:

- SriovNetwork

- OVSNetwork

- SriovNetworkNodeState

- SriovNetworkNodePolicy
Expand Down Expand Up @@ -101,6 +103,43 @@ spec:
}
```

### OVSNetwork

A custom resource of OVSNetwork could represent the a layer-2 broadcast domain attached to Open vSwitch that works in HW-offloading mode.
It is primarily used to generate a NetworkAttachmentDefinition CR with an OVS CNI plugin configuration.

The OVSNetwork CR also contains the `resourceName` which is aligned with the `resourceName` of SR-IOV device plugin. One OVSNetwork obj maps to one `resourceName`, but one `resourceName` can be shared by different OVSNetwork CRs.

It is expected that `resourceName` contains name of the resource pool which holds Virtual Functions of a NIC in the switchdev mode.
A Physical function of the NIC should be attached to an OVS bridge before any workload which uses OVSNetwork starts.

_Note: If `OVSNetwork.Spec.bridge` is not set, then ovs-cni will try to automatically select the right OVS bridge by resolving: VF>PF>OVS-bridge_

Example:

```yaml
apiVersion: sriovnetwork.openshift.io/v1
kind: OVSNetwork
metadata:
name: example-network
namespace: example-namespace
spec:
ipam: |
{
"type": "host-local",
"subnet": "10.56.217.0/24",
"rangeStart": "10.56.217.171",
"rangeEnd": "10.56.217.181",
"routes": [{
"dst": "0.0.0.0/0"
}],
"gateway": "10.56.217.1"
}
vlan: 100
mtu: 2500
resourceName: switchdevnics
```

### SriovNetworkNodeState

The custom resource to represent the SR-IOV interface states of each host, which should only be managed by the operator itself.
Expand Down
67 changes: 65 additions & 2 deletions api/v1/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
Expand Down Expand Up @@ -630,7 +631,7 @@ func (cr *SriovIBNetwork) RenderNetAttDef() (*uns.Unstructured, error) {
data.Data["LogLevelConfigured"] = false
data.Data["LogFileConfigured"] = false

objs, err := render.RenderDir(ManifestsPath, &data)
objs, err := render.RenderDir(filepath.Join(ManifestsPath, "sriov"), &data)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -748,7 +749,7 @@ func (cr *SriovNetwork) RenderNetAttDef() (*uns.Unstructured, error) {
data.Data["LogFileConfigured"] = (cr.Spec.LogFile != "")
data.Data["LogFile"] = cr.Spec.LogFile

objs, err := render.RenderDir(ManifestsPath, &data)
objs, err := render.RenderDir(filepath.Join(ManifestsPath, "sriov"), &data)
if err != nil {
return nil, err
}
Expand All @@ -764,6 +765,68 @@ func (cr *SriovNetwork) NetworkNamespace() string {
return cr.Spec.NetworkNamespace
}

// RenderNetAttDef renders a net-att-def for sriov CNI
func (cr *OVSNetwork) RenderNetAttDef() (*uns.Unstructured, error) {
logger := log.WithName("RenderNetAttDef")
logger.Info("Start to render OVS CNI NetworkAttachmentDefinition")

// render RawCNIConfig manifests
data := render.MakeRenderData()
data.Data["CniType"] = "ovs"
data.Data["NetworkName"] = cr.Name
if cr.Spec.NetworkNamespace == "" {
data.Data["NetworkNamespace"] = cr.Namespace
} else {
data.Data["NetworkNamespace"] = cr.Spec.NetworkNamespace
}
data.Data["CniResourceName"] = os.Getenv("RESOURCE_PREFIX") + "/" + cr.Spec.ResourceName

if cr.Spec.Capabilities == "" {
data.Data["CapabilitiesConfigured"] = false
} else {
data.Data["CapabilitiesConfigured"] = true
data.Data["CniCapabilities"] = cr.Spec.Capabilities
}

data.Data["Bridge"] = cr.Spec.Bridge
data.Data["VlanTag"] = cr.Spec.Vlan
data.Data["MTU"] = cr.Spec.MTU
if len(cr.Spec.Trunk) > 0 {
trunkConfRaw, _ := json.Marshal(cr.Spec.Trunk)
data.Data["Trunk"] = string(trunkConfRaw)
} else {
data.Data["Trunk"] = ""
}
data.Data["InterfaceType"] = cr.Spec.InterfaceType

if cr.Spec.IPAM != "" {
data.Data["CniIpam"] = SriovCniIpam + ":" + strings.Join(strings.Fields(cr.Spec.IPAM), "")
} else {
data.Data["CniIpam"] = SriovCniIpamEmpty
}

data.Data["MetaPluginsConfigured"] = false
if cr.Spec.MetaPluginsConfig != "" {
data.Data["MetaPluginsConfigured"] = true
data.Data["MetaPlugins"] = cr.Spec.MetaPluginsConfig
}

objs, err := render.RenderDir(filepath.Join(ManifestsPath, "ovs"), &data)
if err != nil {
return nil, err
}
for _, obj := range objs {
raw, _ := json.Marshal(obj)
logger.Info("render NetworkAttachmentDefinition output", "raw", string(raw))
}
return objs[0], nil
}

// NetworkNamespace returns target network namespace for the network
func (cr *OVSNetwork) NetworkNamespace() string {
return cr.Spec.NetworkNamespace
}

// NetFilterMatch -- parse netFilter and check for a match
func NetFilterMatch(netFilter string, netValue string) (isMatch bool) {
logger := log.WithName("NetFilterMatch")
Expand Down
93 changes: 93 additions & 0 deletions api/v1/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,99 @@ func TestIBRendering(t *testing.T) {
}
}

func TestOVSRendering(t *testing.T) {
testtable := []struct {
tname string
network v1.OVSNetwork
}{
{
tname: "simpleovs",
network: v1.OVSNetwork{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.OVSNetworkSpec{
NetworkNamespace: "testnamespace",
ResourceName: "testresource",
},
},
},
{
tname: "chained",
network: v1.OVSNetwork{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.OVSNetworkSpec{
NetworkNamespace: "testnamespace",
ResourceName: "testresource",
MTU: 1500,
MetaPluginsConfig: `
{
"type": "vrf",
"vrfname": "blue"
}
`,
},
},
},
{
tname: "complexconf",
network: v1.OVSNetwork{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Spec: v1.OVSNetworkSpec{
NetworkNamespace: "testnamespace",
ResourceName: "testresource",
Capabilities: `{"foo": "bar"}`,
Bridge: "test",
Vlan: 100,
MTU: 1500,
Trunk: []*v1.TrunkConfig{
{
ID: func(i uint) *uint { return &i }(120)},
{
MinID: func(i uint) *uint { return &i }(500),
MaxID: func(i uint) *uint { return &i }(550)},
},
InterfaceType: "netdev",
IPAM: `{"type": "foo"}`,
},
},
},
}
for _, tc := range testtable {
t.Run(tc.tname, func(t *testing.T) {
var b bytes.Buffer
w := bufio.NewWriter(&b)
rendered, err := tc.network.RenderNetAttDef()
if err != nil {
t.Fatal("failed rendering network attachment definition", err)
}
encoder := json.NewEncoder(w)
encoder.SetIndent("", " ")
encoder.Encode(rendered)
w.Flush()
gp := filepath.Join("testdata", filepath.FromSlash(t.Name())+".golden")
if *update {
t.Log("update golden file")
if err := os.WriteFile(gp, b.Bytes(), 0644); err != nil {
t.Fatalf("failed to update golden file: %s", err)
}
}
g, err := os.ReadFile(gp)
if err != nil {
t.Fatalf("failed reading .golden: %s", err)
}
t.Log(b.String())
if !bytes.Equal(b.Bytes(), g) {
t.Errorf("bytes do not match .golden file")
}
})
}
}

func TestSriovNetworkNodePolicyApply(t *testing.T) {
testtable := []struct {
tname string
Expand Down
14 changes: 14 additions & 0 deletions api/v1/testdata/TestOVSRendering/chained.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"apiVersion": "k8s.cni.cncf.io/v1",
"kind": "NetworkAttachmentDefinition",
"metadata": {
"annotations": {
"k8s.v1.cni.cncf.io/resourceName": "/testresource"
},
"name": "test",
"namespace": "testnamespace"
},
"spec": {
"config": "{ \"cniVersion\":\"0.3.1\", \"name\":\"test\",\"plugins\": [ {\"type\":\"ovs\",\"mtu\":1500,\"ipam\":{} },\n{ \"type\": \"vrf\", \"vrfname\": \"blue\" }\n] }"
}
}
14 changes: 14 additions & 0 deletions api/v1/testdata/TestOVSRendering/complexconf.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"apiVersion": "k8s.cni.cncf.io/v1",
"kind": "NetworkAttachmentDefinition",
"metadata": {
"annotations": {
"k8s.v1.cni.cncf.io/resourceName": "/testresource"
},
"name": "test",
"namespace": "testnamespace"
},
"spec": {
"config": "{ \"cniVersion\":\"0.3.1\", \"name\":\"test\",\"type\":\"ovs\",\"capabilities\":{\"foo\": \"bar\"},\"bridge\":\"test\",\"vlan\":100,\"mtu\":1500,\"trunk\":[{\"id\":120},{\"minID\":500,\"maxID\":550}],\"interface_type\":\"netdev\",\"ipam\":{\"type\":\"foo\"} }"
}
}
14 changes: 14 additions & 0 deletions api/v1/testdata/TestOVSRendering/simpleovs.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"apiVersion": "k8s.cni.cncf.io/v1",
"kind": "NetworkAttachmentDefinition",
"metadata": {
"annotations": {
"k8s.v1.cni.cncf.io/resourceName": "/testresource"
},
"name": "test",
"namespace": "testnamespace"
},
"spec": {
"config": "{ \"cniVersion\":\"0.3.1\", \"name\":\"test\",\"type\":\"ovs\",\"ipam\":{} }"
}
}
43 changes: 43 additions & 0 deletions bindata/manifests/cni-config/ovs/ovs-cni-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
name: {{.NetworkName}}
namespace: {{.NetworkNamespace}}
annotations:
k8s.v1.cni.cncf.io/resourceName: {{.CniResourceName}}
spec:
config: '{
"cniVersion":"0.3.1",
"name":"{{.NetworkName}}",
{{- if .MetaPluginsConfigured -}}
"plugins": [
{
{{- end -}}
"type":"{{.CniType}}",
{{- if .CapabilitiesConfigured -}}
"capabilities":{{.CniCapabilities}},
{{- end -}}
{{- if .Bridge -}}
"bridge":"{{.Bridge}}",
{{- end -}}
{{- if .VlanTag -}}
"vlan":{{.VlanTag}},
{{- end -}}
{{- if .MTU -}}
"mtu":{{.MTU}},
{{- end -}}
{{- if .Trunk -}}
"trunk":{{.Trunk}},
{{- end -}}
{{- if .InterfaceType -}}
"interface_type":"{{.InterfaceType}}",
{{- end -}}
{{.CniIpam}}
}
{{- if .MetaPluginsConfigured -}}
,
{{.MetaPlugins}}
]
}
{{- end -}}
'
Loading

0 comments on commit de0caa8

Please sign in to comment.