Skip to content

Commit

Permalink
Merge pull request #579 from stuggi/nad_annotation_gw
Browse files Browse the repository at this point in the history
Add EnsureNetworksAnnotation as an alternative for CreateNetworksAnnotation
  • Loading branch information
stuggi authored Nov 13, 2024
2 parents 71a0e9d + 9e433ca commit 619091d
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 0 deletions.
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.

0 comments on commit 619091d

Please sign in to comment.