Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add network CRDs to cloud-provider-gcp #375

Merged
merged 3 commits into from
Oct 19, 2022
Merged
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
32 changes: 32 additions & 0 deletions crd/apis/network/v1/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "v1",
srcs = [
"annotations.go",
"groupversion_info.go",
"network.go",
"network_types.go",
"networkinterface_types.go",
"zz_generated.deepcopy.go",
],
importpath = "k8s.io/cloud-provider-gcp/crd/apis/network/v1",
visibility = ["//visibility:public"],
deps = [
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:meta",
"//vendor/k8s.io/apimachinery/pkg/runtime",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema",
],
)

go_test(
name = "v1_test",
embed = [":v1"],
srcs = [
"annotations_test.go",
],
deps = [
"//vendor/github.com/google/go-cmp/cmp",
],
)

147 changes: 147 additions & 0 deletions crd/apis/network/v1/annotations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package v1

import (
"encoding/json"
"fmt"
)

// Annotation definitions.
const (
// DisableSourceValidationAnnotationKey is the annotation on pod to disable source IP validation on L2 interfaces.
// Useful when you want to assign new IPs onto the interface.
DisableSourceIPValidationAnnotationKey = "networking.gke.io/disable-source-ip-validation"
// DisableSourceValidationAnnotationValTrue is the value to disable source IP validation for the pod.
DisableSourceIPValidationAnnotationValTrue = "true"
// DefaultInterfaceAnnotationKey specifies the default route interface with interface name in pod.
// The IP of the gateway comes from network CRs.
DefaultInterfaceAnnotationKey = "networking.gke.io/default-interface"
// InterfaceAnnotationKey specifies interfaces for pod.
InterfaceAnnotationKey = "networking.gke.io/interfaces"
// NodeNetworkAnnotationKey is the key of the annotation which indicates the status of
// networks on the node.
NodeNetworkAnnotationKey = "networking.gke.io/network-status"
// PodIPsAnnotationKey is the key of the annotation which indicates additional pod IPs assigned to the pod.
PodIPsAnnotationKey = "networking.gke.io/pod-ips"
// NetworkAnnotationKey is the network annotation on NetworkPolicy object.
// Value for this key will be the network on which network policy should be enforced.
NetworkAnnotationKey = "networking.gke.io/network"
// NetworkInUseAnnotationKey is the annotation on Network object.
// It's used to indicate if the Network object is referenced by NetworkInterface/pod objects.
NetworkInUseAnnotationKey = "networking.gke.io/in-use"
// NetworkInUseAnnotationValTrue is the value to be set for NetworkInUseAnnotationKey to indicate
// the Network object is referenced by at least one NetworkInterface/pod object.
NetworkInUseAnnotationValTrue = "true"
// MultiNetworkAnnotationKey is the network annotation key used to hold network data per node, eg: PodCIDRs.
MultiNetworkAnnotationKey = "networking.gke.io/networks"
// AutoGenAnnotationKey is to indicate if the object is auto-generated.
AutoGenAnnotationKey = "networking.gke.io/auto-generated"
// AutoGenAnnotationValTrue is the value to be set for auto-generated objects.
AutoGenAnnotationValTrue = "true"
)

// InterfaceAnnotation is the value of the interface annotation.
// +kubebuilder:object:generate:=false
type InterfaceAnnotation []InterfaceRef

// InterfaceRef specifies the reference to network interface.
// All fields are mutual exclusive.
// Either Network or Interface field can be specified.
// +kubebuilder:object:generate:=false
type InterfaceRef struct {
// InterfaceName is the name of the interface in pod network namespace.
InterfaceName string `json:"interfaceName,omitempty"`
// Network refers to a network object within the cluster.
// When network is specified, NetworkInterface object is optionally generated with default configuration.
Network *string `json:"network,omitempty"`
// Interface reference the NetworkInterface object within the namespace.
Interface *string `json:"interface,omitempty"`
}

// ParseInterfaceAnnotation parses the given annotation.
func ParseInterfaceAnnotation(annotation string) (InterfaceAnnotation, error) {
ret := &InterfaceAnnotation{}
err := json.Unmarshal([]byte(annotation), ret)
return *ret, err
}

// MarshalAnnotation marshals any object into string using json.Marshal.
func MarshalAnnotation(a interface{}) (string, error) {
ret, err := json.Marshal(a)
if err != nil {
return "", fmt.Errorf("failed to marshal (%+v): %v", a, err)
}
return string(ret), nil
}

// NodeNetworkAnnotation is the value of the network status annotation.
// +kubebuilder:object:generate:=false
type NodeNetworkAnnotation []NodeNetworkStatus

// PodIPsAnnotation is the value of the pod IPs annotation.
// +kubebuilder:object:generate:=false
type PodIPsAnnotation []PodIP

// MultiNetworkAnnotation is the value of networks annotation.
// +kubebuilder:object:generate:=false
type MultiNetworkAnnotation []NodeNetwork

// NodeNetworkStatus specifies the status of a network.
// +kubebuilder:object:generate:=false
type NodeNetworkStatus struct {
// Name specifies the name of the network.
Name string `json:"name,omitempty"`

// IPv4Subnet is the Node internal IPv4 subnet for the network.
IPv4Subnet string `json:"ipv4-subnet,omitempty"`

// IPv6Subnet is the Node internal IPv6 subnet for the network.
IPv6Subnet string `json:"ipv6-subnet,omitempty"`
}

// PodIP specifies the additional pod IPs assigned to the pod.
// This will eventually be merged into the `podIPs` field in PodStatus, so the fields must remain compatible.
// +kubebuilder:object:generate:=false
type PodIP struct {
// NetworkName refers to the network object associated with this IP.
NetworkName string `json:"networkName"`

// IP is an IP address (IPv4 or IPv6) assigned to the pod.
IP string `json:"ip"`
}

// NodeNetwork specifies network data on a node.
// +kubebuilder:object:generate:=false
type NodeNetwork struct {
// Name specifies the name of the network.
Name string `json:"name"`
// Cidrs denotes the IPv4/IPv6 ranges of the network.
Cidrs []string `json:"cidrs"`
// Scope specifies if the network is local to a node or global across a node pool.
Scope string `json:"scope"`
}

// ParseNodeNetworkAnnotation parses the given annotation to NodeNetworkAnnotation.
func ParseNodeNetworkAnnotation(annotation string) (NodeNetworkAnnotation, error) {
ret := &NodeNetworkAnnotation{}
err := json.Unmarshal([]byte(annotation), ret)
return *ret, err
}

// ParsePodIPsAnnotation parses the given annotation to PodIPsAnnotation.
func ParsePodIPsAnnotation(annotation string) (PodIPsAnnotation, error) {
ret := &PodIPsAnnotation{}
err := json.Unmarshal([]byte(annotation), ret)
return *ret, err
}

// ParseMultiNetworkAnnotation parses given annotation to MultiNetworkAnnotation.
func ParseMultiNetworkAnnotation(annotation string) (MultiNetworkAnnotation, error) {
ret := &MultiNetworkAnnotation{}
err := json.Unmarshal([]byte(annotation), ret)
return *ret, err
}

// MarshalNodeNetworkAnnotation marshals a NodeNetworkAnnotation into string.
func MarshalNodeNetworkAnnotation(a NodeNetworkAnnotation) (string, error) {
return MarshalAnnotation(a)
}
173 changes: 173 additions & 0 deletions crd/apis/network/v1/annotations_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package v1

import (
"testing"

"github.com/google/go-cmp/cmp"
)

func TestPodIPsAnnotation(t *testing.T) {
tests := []struct {
name string
input PodIPsAnnotation
expected string
}{
{
name: "nil",
input: nil,
expected: "null",
},
{
name: "empty list",
input: PodIPsAnnotation{},
expected: "[]",
},
{
name: "single pod IP",
input: PodIPsAnnotation{
{NetworkName: "network-a", IP: "198.51.100.0"},
},
expected: `[{"networkName":"network-a","ip":"198.51.100.0"}]`,
},
{
name: "missing network",
input: PodIPsAnnotation{
{IP: "198.51.100.0"},
},
expected: `[{"networkName":"","ip":"198.51.100.0"}]`,
},
{
name: "multiple pod IPs",
input: PodIPsAnnotation{
{NetworkName: "network-a", IP: "198.51.100.0"},
{NetworkName: "network-b", IP: "2001:db8::"},
},
expected: `[{"networkName":"network-a","ip":"198.51.100.0"},{"networkName":"network-b","ip":"2001:db8::"}]`,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
marshalled, err := MarshalAnnotation(tc.input)
if err != nil {
t.Fatalf("MarshalAnnotation(%+v) failed with error: %v", tc.input, err)
}
if marshalled != tc.expected {
t.Fatalf("MarshalAnnotation(%+v) returns %q but want %q", tc.input, marshalled, tc.expected)
}

parsed, err := ParsePodIPsAnnotation(marshalled)
if err != nil {
t.Fatalf("ParsePodIPsAnnotation(%s) failed with error: %v", marshalled, err)
}

if diff := cmp.Diff(parsed, tc.input); diff != "" {
t.Fatalf("ParsePodIPsAnnotation(%s) returns diff: (-got +want): %s", marshalled, diff)
}
})
}
}

func TestNodeNetworkAnnotation(t *testing.T) {
tests := []struct {
name string
input NodeNetworkAnnotation
expected string
}{
{
name: "nil",
input: nil,
expected: "null",
},
{
name: "empty list",
input: NodeNetworkAnnotation{},
expected: "[]",
},
{
name: "list with items",
input: NodeNetworkAnnotation{
{Name: "network-a"},
{Name: "network-b"},
},
expected: `[{"name":"network-a"},{"name":"network-b"}]`,
},
{
name: "list with items with subnets",
input: NodeNetworkAnnotation{
{Name: "network-a", IPv4Subnet: "198.51.100.0/24", IPv6Subnet: "2001:db8::/32"},
{Name: "network-b", IPv4Subnet: "198.52.100.0/24", IPv6Subnet: "2001:db9::/32"},
},
expected: `[{"name":"network-a","ipv4-subnet":"198.51.100.0/24","ipv6-subnet":"2001:db8::/32"},{"name":"network-b","ipv4-subnet":"198.52.100.0/24","ipv6-subnet":"2001:db9::/32"}]`,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
marshalled, err := MarshalAnnotation(tc.input)
if err != nil {
t.Fatalf("MarshalAnnotation(%+v) failed with error: %v", tc.input, err)
}
if marshalled != tc.expected {
t.Fatalf("MarshalAnnotation(%+v) returns %q but want %q", tc.input, marshalled, tc.expected)
}

parsed, err := ParseNodeNetworkAnnotation(marshalled)
if err != nil {
t.Fatalf("ParseNodeNetworkAnnotation(%s) failed with error: %v", marshalled, err)
}

if diff := cmp.Diff(parsed, tc.input); diff != "" {
t.Fatalf("ParseNodeNetworkAnnotation(%s) returns diff: (-got +want): %s", marshalled, diff)
}
})
}
}

func TestParseMultiNetworkAnnotation(t *testing.T) {
tests := []struct {
name string
input MultiNetworkAnnotation
expected string
}{
{
name: "nil",
input: nil,
expected: "null",
},
{
name: "empty list",
input: MultiNetworkAnnotation{},
expected: "[]",
},
{
name: "list with items",
input: MultiNetworkAnnotation{
{Name: "network-a", Cidrs: []string{"1.1.1.1/21"}, Scope: "host-local"},
{Name: "network-b", Cidrs: []string{"2.2.2.2/12"}, Scope: "global"},
},
expected: `[{"name":"network-a","cidrs":["1.1.1.1/21"],"scope":"host-local"},{"name":"network-b","cidrs":["2.2.2.2/12"],"scope":"global"}]`,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
marshalled, err := MarshalAnnotation(tc.input)
if err != nil {
t.Fatalf("MarshalAnnotation(%+v) failed with error: %v", tc.input, err)
}
if marshalled != tc.expected {
t.Fatalf("MarshalAnnotation(%+v) returns %q but want %q", tc.input, marshalled, tc.expected)
}

parsed, err := ParseMultiNetworkAnnotation(marshalled)
if err != nil {
t.Fatalf("ParseMultiNetworkAnnotation(%s) failed with error: %v", marshalled, err)
}

if diff := cmp.Diff(parsed, tc.input); diff != "" {
t.Fatalf("ParseMultiNetworkAnnotation(%s) returns diff: (-got +want): %s", marshalled, diff)
}
})
}
}
42 changes: 42 additions & 0 deletions crd/apis/network/v1/groupversion_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Package v1 contains API Schema definitions for the networking v1 API group
// +kubebuilder:object:generate=true
// +groupName=networking.gke.io

package v1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)

var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "networking.gke.io", Version: "v1"}

schemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = schemeBuilder.AddToScheme
)

// Kind names for the Network objects.
const (
KindNetwork = "Network"
KindNetworkInterface = "NetworkInterface"
)

// Adds the list of known types to Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(GroupVersion,
&Network{},
&NetworkList{},
&NetworkInterface{},
&NetworkInterfaceList{},
)
metav1.AddToGroupVersion(scheme, GroupVersion)
return nil
}

// Kind takes an unqualified kind and returns back a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return GroupVersion.WithKind(kind).GroupKind()
}
Loading