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 EnsureNetworksAnnotation as an alternative for CreateNetworksAnnotation #579

Merged
merged 1 commit into from
Nov 13, 2024
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
68 changes: 68 additions & 0 deletions modules/common/networkattachment/networkattachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@ limitations under the License.
package networkattachment

import (
"bytes"
"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"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/util/jsonpath"
)

// GetNADWithName - Get network-attachment-definition with name in namespace
Expand All @@ -49,6 +52,7 @@ func GetNADWithName(

// CreateNetworksAnnotation returns pod annotation for network-attachment-definition list
// e.g. k8s.v1.cni.cncf.io/networks: '[{"name": "internalapi", "namespace": "openstack"},{"name": "storage", "namespace": "openstack"}]'
// NOTE: Deprecated, use EnsureNetworksAnnotation
func CreateNetworksAnnotation(namespace string, nads []string) (map[string]string, error) {

netAnnotations := []networkv1.NetworkSelectionElement{}
Expand Down Expand Up @@ -136,3 +140,67 @@ 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 will contain the `default-route` for that network:
// e.g. k8s.v1.cni.cncf.io/networks: '[{"name":"internalapi","namespace":"openstack","interface":"internalapi","default-route":["10.1.2.200"]}]'
func EnsureNetworksAnnotation(
nadList []networkv1.NetworkAttachmentDefinition,
) (map[string]string, error) {

annotationString := map[string]string{}
netAnnotations := []networkv1.NetworkSelectionElement{}
for _, nad := range nadList {
gateway := ""

var data interface{}
if err := json.Unmarshal([]byte(nad.Spec.Config), &data); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON data: %w", err)
}

// use jsonpath to parse the cni config
jp := jsonpath.New(nad.Name)
jp.AllowMissingKeys(true) // Allow missing keys, for when no gateway configured

// Parse the JSONPath template, for now just `ipam.gateway`
err := jp.Parse(`{.ipam.gateway}`)
if err != nil {
return annotationString, fmt.Errorf("parse template error: %w", err)
}

buf := new(bytes.Buffer)
// get the gateway from the config
err = jp.Execute(buf, data)
if err != nil {
return annotationString, fmt.Errorf("parse execute template against nad %+v error: %w", nad.Spec.Config, err)
}

gateway = buf.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
}
122 changes: 122 additions & 0 deletions modules/common/networkattachment/networkattachment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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))
})
}
}
5 changes: 5 additions & 0 deletions modules/storage/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading