Skip to content

Commit 9e433ca

Browse files
committed
Add EnsureNetworksAnnotation as an alternative for CreateNetworksAnnotation
For BGP setup there is the need to set the default gateway to the additional interface defined via the multus annotations. To allow this a user can configure `ipam.gateway` in the NAD. CreateNetworksAnnotation() will override the pod network default route by reading the NAD. If `ipam.gateway` is defined and not "", it gets set on the networks annotation as the `default-route`. Jira: https://issues.redhat.com/browse/OSPRH-8680 Signed-off-by: Martin Schuppert <mschuppert@redhat.com>
1 parent 71a0e9d commit 9e433ca

3 files changed

Lines changed: 195 additions & 0 deletions

File tree

modules/common/networkattachment/networkattachment.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,17 @@ limitations under the License.
1717
package networkattachment
1818

1919
import (
20+
"bytes"
2021
"context"
2122
"encoding/json"
2223
"fmt"
24+
"net"
2325

2426
networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
2527
"github.com/openstack-k8s-operators/lib-common/modules/common/helper"
2628
"github.com/openstack-k8s-operators/lib-common/modules/common/pod"
2729
"k8s.io/apimachinery/pkg/types"
30+
"k8s.io/client-go/util/jsonpath"
2831
)
2932

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

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

5458
netAnnotations := []networkv1.NetworkSelectionElement{}
@@ -136,3 +140,67 @@ func VerifyNetworkStatusFromAnnotation(
136140

137141
return networkReady, networkAttachmentStatus, nil
138142
}
143+
144+
// EnsureNetworksAnnotation returns pod annotation for network-attachment-definition list
145+
// e.g. k8s.v1.cni.cncf.io/networks: '[{"name": "internalapi", "namespace": "openstack"},{"name": "storage", "namespace": "openstack"}]'
146+
// If `ipam.gateway` is defined in the NAD, the annotation will contain the `default-route` for that network:
147+
// e.g. k8s.v1.cni.cncf.io/networks: '[{"name":"internalapi","namespace":"openstack","interface":"internalapi","default-route":["10.1.2.200"]}]'
148+
func EnsureNetworksAnnotation(
149+
nadList []networkv1.NetworkAttachmentDefinition,
150+
) (map[string]string, error) {
151+
152+
annotationString := map[string]string{}
153+
netAnnotations := []networkv1.NetworkSelectionElement{}
154+
for _, nad := range nadList {
155+
gateway := ""
156+
157+
var data interface{}
158+
if err := json.Unmarshal([]byte(nad.Spec.Config), &data); err != nil {
159+
return nil, fmt.Errorf("failed to unmarshal JSON data: %w", err)
160+
}
161+
162+
// use jsonpath to parse the cni config
163+
jp := jsonpath.New(nad.Name)
164+
jp.AllowMissingKeys(true) // Allow missing keys, for when no gateway configured
165+
166+
// Parse the JSONPath template, for now just `ipam.gateway`
167+
err := jp.Parse(`{.ipam.gateway}`)
168+
if err != nil {
169+
return annotationString, fmt.Errorf("parse template error: %w", err)
170+
}
171+
172+
buf := new(bytes.Buffer)
173+
// get the gateway from the config
174+
err = jp.Execute(buf, data)
175+
if err != nil {
176+
return annotationString, fmt.Errorf("parse execute template against nad %+v error: %w", nad.Spec.Config, err)
177+
}
178+
179+
gateway = buf.String()
180+
181+
gatewayReq := []net.IP{}
182+
if gateway != "" {
183+
gatewayReq = append(gatewayReq, net.ParseIP(gateway))
184+
185+
}
186+
187+
netAnnotations = append(
188+
netAnnotations,
189+
networkv1.NetworkSelectionElement{
190+
Name: nad.Name,
191+
Namespace: nad.Namespace,
192+
InterfaceRequest: GetNetworkIFName(nad.Name),
193+
GatewayRequest: gatewayReq,
194+
},
195+
)
196+
}
197+
198+
networks, err := json.Marshal(netAnnotations)
199+
if err != nil {
200+
return nil, fmt.Errorf("failed to encode networks %v into json: %w", nadList, err)
201+
}
202+
203+
annotationString[networkv1.NetworkAttachmentAnnot] = string(networks)
204+
205+
return annotationString, nil
206+
}

modules/common/networkattachment/networkattachment_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"testing"
2121

2222
networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2324

2425
. "github.com/onsi/gomega"
2526
)
@@ -169,3 +170,124 @@ func TestGetNetworkIFName(t *testing.T) {
169170
})
170171
}
171172
}
173+
174+
func TestEnsureNetworksAnnotation(t *testing.T) {
175+
176+
tests := []struct {
177+
name string
178+
nadList []networkv1.NetworkAttachmentDefinition
179+
want map[string]string
180+
}{
181+
{
182+
name: "No network",
183+
nadList: []networkv1.NetworkAttachmentDefinition{},
184+
want: map[string]string{networkv1.NetworkAttachmentAnnot: "[]"},
185+
},
186+
{
187+
name: "Single network",
188+
nadList: []networkv1.NetworkAttachmentDefinition{
189+
{
190+
ObjectMeta: metav1.ObjectMeta{Name: "one", Namespace: "foo"},
191+
Spec: networkv1.NetworkAttachmentDefinitionSpec{
192+
Config: `
193+
{
194+
"cniVersion": "0.3.1",
195+
"name": "internalapi",
196+
"type": "macvlan",
197+
"master": "internalapi",
198+
"ipam": {
199+
"type": "whereabouts",
200+
"range": "172.17.0.0/24",
201+
"range_start": "172.17.0.30",
202+
"range_end": "172.17.0.70"
203+
}
204+
}
205+
`,
206+
},
207+
},
208+
},
209+
want: map[string]string{networkv1.NetworkAttachmentAnnot: "[{\"name\":\"one\",\"namespace\":\"foo\",\"interface\":\"one\"}]"},
210+
},
211+
{
212+
name: "Multiple networks",
213+
nadList: []networkv1.NetworkAttachmentDefinition{
214+
{
215+
ObjectMeta: metav1.ObjectMeta{Name: "one", Namespace: "foo"},
216+
Spec: networkv1.NetworkAttachmentDefinitionSpec{
217+
Config: `
218+
{
219+
"cniVersion": "0.3.1",
220+
"name": "internalapi",
221+
"type": "macvlan",
222+
"master": "internalapi",
223+
"ipam": {
224+
"type": "whereabouts",
225+
"range": "172.17.0.0/24",
226+
"range_start": "172.17.0.30",
227+
"range_end": "172.17.0.70"
228+
}
229+
}
230+
`,
231+
},
232+
},
233+
{
234+
ObjectMeta: metav1.ObjectMeta{Name: "two", Namespace: "foo"},
235+
Spec: networkv1.NetworkAttachmentDefinitionSpec{
236+
Config: `
237+
{
238+
"cniVersion": "0.3.1",
239+
"name": "tenant",
240+
"type": "macvlan",
241+
"master": "tenant",
242+
"ipam": {
243+
"type": "whereabouts",
244+
"range": "172.19.0.0/24",
245+
"range_start": "172.19.0.30",
246+
"range_end": "172.19.0.70"
247+
}
248+
}
249+
`,
250+
},
251+
},
252+
},
253+
want: map[string]string{networkv1.NetworkAttachmentAnnot: "[{\"name\":\"one\",\"namespace\":\"foo\",\"interface\":\"one\"},{\"name\":\"two\",\"namespace\":\"foo\",\"interface\":\"two\"}]"},
254+
},
255+
{
256+
name: "With gateway defined",
257+
nadList: []networkv1.NetworkAttachmentDefinition{
258+
{
259+
ObjectMeta: metav1.ObjectMeta{Name: "one", Namespace: "foo"},
260+
Spec: networkv1.NetworkAttachmentDefinitionSpec{
261+
Config: `
262+
{
263+
"cniVersion": "0.3.1",
264+
"name": "internalapi",
265+
"type": "macvlan",
266+
"master": "internalapi",
267+
"ipam": {
268+
"type": "whereabouts",
269+
"range": "172.17.0.0/24",
270+
"range_start": "172.17.0.30",
271+
"range_end": "172.17.0.70",
272+
"gateway": "172.17.0.1"
273+
}
274+
}
275+
`,
276+
},
277+
},
278+
},
279+
want: map[string]string{networkv1.NetworkAttachmentAnnot: "[{\"name\":\"one\",\"namespace\":\"foo\",\"interface\":\"one\",\"default-route\":[\"172.17.0.1\"]}]"},
280+
},
281+
}
282+
283+
for _, tt := range tests {
284+
t.Run(tt.name, func(t *testing.T) {
285+
g := NewWithT(t)
286+
287+
networkAnnotation, err := EnsureNetworksAnnotation(tt.nadList)
288+
g.Expect(err).NotTo(HaveOccurred())
289+
g.Expect(networkAnnotation).To(HaveLen(len(tt.want)))
290+
g.Expect(networkAnnotation).To(BeEquivalentTo(tt.want))
291+
})
292+
}
293+
}

modules/storage/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)