Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ on:
workflow_dispatch:

env:
GO_VERSION: "1.23"
K8S_VERSION: "v1.32.0"
KIND_VERSION: "v0.25.0"
GO_VERSION: "1.24"
K8S_VERSION: "v1.32.2"
KIND_VERSION: "v0.27.0"
IMAGE_NAME: ghcr.io/google/dranet
KIND_CLUSTER_NAME: kind

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ require (
k8s.io/klog/v2 v2.130.1
k8s.io/kubelet v0.32.3
k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e
sigs.k8s.io/yaml v1.4.0
)

require (
Expand Down Expand Up @@ -93,5 +94,4 @@ require (
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)
76 changes: 76 additions & 0 deletions pkg/apis/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
Copyright 2025 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package apis

// TODO Generate code and keep in sync golang types on schema
type NetworkConfig struct {
Name string `json:"name"` // new name inside the namespace
IPs []string `json:"ips"`
Routes []Route `json:"routes"`
MTU int `json:"mtu"`
Mode Mode `json:"mode"`
Macvlan *MacvlanConfig `json:"macvlan,omitempty"`
Macvtap *MacvlanConfig `json:"macvtap,omitempty"`
IPvlan *IPvlanConfig `json:"ipvlan,omitempty"`
}

// Route represents a route configuration.
type Route struct {
Destination string `json:"destination"`
Gateway string `json:"gateway"`
}

// Mode represents the network mode.
type Mode string

// Enumerated Mode values.
const (
ModeMacvlan Mode = "macvlan"
ModeMacvtap Mode = "macvtap"
ModeIPvlan Mode = "ipvlan"
ModeDedicated Mode = "dedicated"
)

// MacvlanConfig represents the Macvlan configuration.
type MacvlanConfig struct {
Mode MacvlanMode `json:"macvlanMode"`
}

// MacvlanMode represents the macvlan mode.
type MacvlanMode string

// Enumerated Macvlan mode values.
const (
MacvlanModeBridge MacvlanMode = "bridge"
MacvlanModePrivate MacvlanMode = "private"
MacvlanModeVepa MacvlanMode = "vepa"
MacvlanModePassthru MacvlanMode = "passthru"
)

// IPvlanConfig represents the IPvlan configuration.
type IPvlanConfig struct {
Mode IPvlanMode `json:"ipvlanMode"`
}

// IPvlanMode represents the ipvlan mode.
type IPvlanMode string

// Enumerated IPvlan mode values.
const (
IPvlanModeL2 IPvlanMode = "l2"
IPvlanModeL3 IPvlanMode = "l3"
)
78 changes: 78 additions & 0 deletions pkg/apis/validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
Copyright 2025 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package apis

import (
"errors"
"fmt"
"net/netip"

"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/yaml"
)

// ValidateConfig validates the data in a runtime.RawExtension against the OpenAPI schema.
func ValidateConfig(raw *runtime.RawExtension) (*NetworkConfig, error) {
if raw == nil || raw.Raw == nil {
return nil, nil
}
// Check if raw.Raw is empty
if len(raw.Raw) == 0 {
return nil, nil
}
var errorsList []error
var config NetworkConfig
if err := yaml.Unmarshal(raw.Raw, &config, yaml.DisallowUnknownFields); err != nil {
return nil, fmt.Errorf("failed to unmarshal YAML data: %w", err)
}

switch config.Mode {
case ModeMacvtap:
if config.Macvtap == nil {
return nil, fmt.Errorf("vlan config is missing")
}
case ModeMacvlan:
if config.Macvlan == nil {
errorsList = append(errorsList, fmt.Errorf("macvlan config is missing"))
}
case ModeIPvlan:
if config.IPvlan == nil {
errorsList = append(errorsList, fmt.Errorf("ipvlan config is missing"))
}
default:
// No mode specified or ModeDedicated
}

for _, ip := range config.IPs {
if _, err := netip.ParsePrefix(ip); err != nil {
errorsList = append(errorsList, fmt.Errorf("invalid IP in CIDR format %s", ip))
}
}

for _, route := range config.Routes {
if route.Destination == "" || route.Gateway == "" {
errorsList = append(errorsList, fmt.Errorf("invalid route %v", route))
}
if _, err := netip.ParsePrefix(route.Destination); err != nil {
errorsList = append(errorsList, fmt.Errorf("invalid CIDR %s", route.Destination))
}
if _, err := netip.ParseAddr(route.Gateway); err != nil {
errorsList = append(errorsList, fmt.Errorf("invalid IP address %s", route.Gateway))
}
}
return &config, errors.Join(errorsList...)
}
173 changes: 173 additions & 0 deletions pkg/apis/validation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
Copyright 2025 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package apis

import (
"testing"

"k8s.io/apimachinery/pkg/runtime"
)

func TestValidateConfig(t *testing.T) {
testCases := []struct {
name string
config string
wantErr bool
}{
{
name: "valid config",
config: `
ips:
- 192.168.1.10/24
routes:
- destination: 10.0.0.0/8
gateway: 192.168.1.1
mtu: 1500
`,
wantErr: false,
},
{
name: "invalid ip",
config: `
ips:
- a.b.c.d/24
routes:
- destination: 10.0.0.0/8
gateway: 192.168.1.1
mtu: 1500
`,
wantErr: true,
},
{
name: "invalid route destination",
config: `
ips:
- 192.168.1.10/24
routes:
- destination: a.b.c.d/8
gateway: 192.168.1.1
mtu: 1500
`,
wantErr: true,
},
{
name: "Empty config",
config: ``,
wantErr: false,
},
{
name: "invalid route",
config: `
ips:
- 192.168.1.10/24
routes:
- destination: 10.0.0.0/8
mtu: 1500
`,
wantErr: true,
},
{
name: "invalid route",
config: `
ips:
- 192.168.1.10/24
routes:
- gateway: 192.168.1.1
mtu: 1500
`,
wantErr: true,
},
{
name: "invalid route gateway",
config: `
ips:
- 192.168.1.10/24
routes:
- destination: 10.0.0.0/8
gateway: a.b.c.d
mtu: 1500
`,
wantErr: true,
},
{
name: "invalid yaml",
config: `
ips:
- 192.168.1.10/24
routes:
- destination: 10.0.0.0/8
gateway: 192.168.1.1
mtu: 1500
foo:
- bar
`,
wantErr: true,
},
{
name: "valid config with name",
config: `
ips:
- 192.168.1.10/24
routes:
- destination: 10.0.0.0/8
gateway: 192.168.1.1
name: eth1
mtu: 1500
`,
wantErr: false,
},
{
name: "valid config with ipv6",
config: `
ips:
- 2001:db8::1/64
routes:
- destination: 2001:db8:1::/64
gateway: 2001:db8::2
name: eth1
mtu: 1500
`,
wantErr: false,
},
{
name: "invalid config with ipv6",
config: `
ips:
- 2001:db8::1/64
routes:
- destination: 2001:db8:1::/64
gateway: 2001:db8::z
name: eth1
mtu: 1500
`,
wantErr: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
raw := &runtime.RawExtension{}
raw.Raw = []byte(tc.config)

_, err := ValidateConfig(raw)
if (err != nil) != tc.wantErr {
t.Errorf("ValidateConfig() error = %v, wantErr %v", err, tc.wantErr)
return
}
})
}
}
Loading
Loading