diff --git a/modules/common/go.mod b/modules/common/go.mod index 2b5a7181..a6c26273 100644 --- a/modules/common/go.mod +++ b/modules/common/go.mod @@ -10,6 +10,7 @@ require ( github.com/onsi/gomega v1.34.1 github.com/openshift/api v3.9.0+incompatible github.com/pkg/errors v0.9.1 + github.com/tidwall/gjson v1.18.0 go.uber.org/zap v1.27.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.29.10 @@ -52,6 +53,8 @@ require ( github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/net v0.28.0 // indirect diff --git a/modules/common/go.sum b/modules/common/go.sum index 5345d3b8..1e6b58b7 100644 --- a/modules/common/go.sum +++ b/modules/common/go.sum @@ -105,6 +105,12 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= diff --git a/modules/common/networkattachment/networkattachment.go b/modules/common/networkattachment/networkattachment.go index c237b3b1..3fa82a53 100644 --- a/modules/common/networkattachment/networkattachment.go +++ b/modules/common/networkattachment/networkattachment.go @@ -20,10 +20,12 @@ import ( "context" "encoding/json" "fmt" + "net" networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" "github.com/openstack-k8s-operators/lib-common/modules/common/helper" "github.com/openstack-k8s-operators/lib-common/modules/common/pod" + "github.com/tidwall/gjson" "k8s.io/apimachinery/pkg/types" ) @@ -136,3 +138,47 @@ func VerifyNetworkStatusFromAnnotation( return networkReady, networkAttachmentStatus, nil } + +// EnsureNetworksAnnotation returns pod annotation for network-attachment-definition list +// e.g. k8s.v1.cni.cncf.io/networks: '[{"name": "internalapi", "namespace": "openstack"},{"name": "storage", "namespace": "openstack"}]' +// If `ipam.gateway` is defined in the NAD, the annotation wil contain +func EnsureNetworksAnnotation( + nadList []networkv1.NetworkAttachmentDefinition, +) (map[string]string, error) { + + annotationString := map[string]string{} + netAnnotations := []networkv1.NetworkSelectionElement{} + for _, nad := range nadList { + cniConfig := nad.Spec.Config + gateway := "" + if gjson.Get(cniConfig, "ipam.gateway").Exists() && + gjson.Get(cniConfig, "ipam.gateway").String() != "" { + gateway = gjson.Get(cniConfig, "ipam.gateway").String() + } + + gatewayReq := []net.IP{} + if gateway != "" { + gatewayReq = append(gatewayReq, net.ParseIP(gateway)) + + } + + netAnnotations = append( + netAnnotations, + networkv1.NetworkSelectionElement{ + Name: nad.Name, + Namespace: nad.Namespace, + InterfaceRequest: GetNetworkIFName(nad.Name), + GatewayRequest: gatewayReq, + }, + ) + } + + networks, err := json.Marshal(netAnnotations) + if err != nil { + return nil, fmt.Errorf("failed to encode networks %v into json: %w", nadList, err) + } + + annotationString[networkv1.NetworkAttachmentAnnot] = string(networks) + + return annotationString, nil +} diff --git a/modules/common/networkattachment/networkattachment_test.go b/modules/common/networkattachment/networkattachment_test.go index 9434b338..ff558719 100644 --- a/modules/common/networkattachment/networkattachment_test.go +++ b/modules/common/networkattachment/networkattachment_test.go @@ -20,6 +20,7 @@ import ( "testing" networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" . "github.com/onsi/gomega" ) @@ -169,3 +170,124 @@ func TestGetNetworkIFName(t *testing.T) { }) } } + +func TestEnsureNetworksAnnotation(t *testing.T) { + + tests := []struct { + name string + nadList []networkv1.NetworkAttachmentDefinition + want map[string]string + }{ + { + name: "No network", + nadList: []networkv1.NetworkAttachmentDefinition{}, + want: map[string]string{networkv1.NetworkAttachmentAnnot: "[]"}, + }, + { + name: "Single network", + nadList: []networkv1.NetworkAttachmentDefinition{ + { + ObjectMeta: metav1.ObjectMeta{Name: "one", Namespace: "foo"}, + Spec: networkv1.NetworkAttachmentDefinitionSpec{ + Config: ` +{ + "cniVersion": "0.3.1", + "name": "internalapi", + "type": "macvlan", + "master": "internalapi", + "ipam": { + "type": "whereabouts", + "range": "172.17.0.0/24", + "range_start": "172.17.0.30", + "range_end": "172.17.0.70" + } +} +`, + }, + }, + }, + want: map[string]string{networkv1.NetworkAttachmentAnnot: "[{\"name\":\"one\",\"namespace\":\"foo\",\"interface\":\"one\"}]"}, + }, + { + name: "Multiple networks", + nadList: []networkv1.NetworkAttachmentDefinition{ + { + ObjectMeta: metav1.ObjectMeta{Name: "one", Namespace: "foo"}, + Spec: networkv1.NetworkAttachmentDefinitionSpec{ + Config: ` +{ + "cniVersion": "0.3.1", + "name": "internalapi", + "type": "macvlan", + "master": "internalapi", + "ipam": { + "type": "whereabouts", + "range": "172.17.0.0/24", + "range_start": "172.17.0.30", + "range_end": "172.17.0.70" + } +} +`, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{Name: "two", Namespace: "foo"}, + Spec: networkv1.NetworkAttachmentDefinitionSpec{ + Config: ` +{ + "cniVersion": "0.3.1", + "name": "tenant", + "type": "macvlan", + "master": "tenant", + "ipam": { + "type": "whereabouts", + "range": "172.19.0.0/24", + "range_start": "172.19.0.30", + "range_end": "172.19.0.70" + } +} +`, + }, + }, + }, + want: map[string]string{networkv1.NetworkAttachmentAnnot: "[{\"name\":\"one\",\"namespace\":\"foo\",\"interface\":\"one\"},{\"name\":\"two\",\"namespace\":\"foo\",\"interface\":\"two\"}]"}, + }, + { + name: "With gateway defined", + nadList: []networkv1.NetworkAttachmentDefinition{ + { + ObjectMeta: metav1.ObjectMeta{Name: "one", Namespace: "foo"}, + Spec: networkv1.NetworkAttachmentDefinitionSpec{ + Config: ` +{ + "cniVersion": "0.3.1", + "name": "internalapi", + "type": "macvlan", + "master": "internalapi", + "ipam": { + "type": "whereabouts", + "range": "172.17.0.0/24", + "range_start": "172.17.0.30", + "range_end": "172.17.0.70", + "gateway": "172.17.0.1" + } +} +`, + }, + }, + }, + want: map[string]string{networkv1.NetworkAttachmentAnnot: "[{\"name\":\"one\",\"namespace\":\"foo\",\"interface\":\"one\",\"default-route\":[\"172.17.0.1\"]}]"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + networkAnnotation, err := EnsureNetworksAnnotation(tt.nadList) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(networkAnnotation).To(HaveLen(len(tt.want))) + g.Expect(networkAnnotation).To(BeEquivalentTo(tt.want)) + }) + } +} diff --git a/modules/storage/zz_generated.deepcopy.go b/modules/storage/zz_generated.deepcopy.go index 59603de0..8130066b 100644 --- a/modules/storage/zz_generated.deepcopy.go +++ b/modules/storage/zz_generated.deepcopy.go @@ -143,6 +143,11 @@ func (in *VolumeSource) DeepCopyInto(out *VolumeSource) { *out = new(v1.CSIVolumeSource) (*in).DeepCopyInto(*out) } + if in.Projected != nil { + in, out := &in.Projected, &out.Projected + *out = new(v1.ProjectedVolumeSource) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeSource.