Skip to content

Commit

Permalink
cni: allow users to set CNI args in job spec (#23538)
Browse files Browse the repository at this point in the history
  • Loading branch information
martisah authored Jul 12, 2024
1 parent 3f2729f commit 661011f
Show file tree
Hide file tree
Showing 14 changed files with 535 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .changelog/23538.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
cni: allow users to input CNI args in job specification
```
6 changes: 5 additions & 1 deletion api/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ type DNSConfig struct {
Searches []string `mapstructure:"searches" hcl:"searches,optional"`
Options []string `mapstructure:"options" hcl:"options,optional"`
}
type CNIConfig struct {
Args map[string]string `hcl:"args,optional"`
}

// NetworkResource is used to describe required network
// resources of a given task.
Expand All @@ -160,7 +163,8 @@ type NetworkResource struct {
// XXX Deprecated. Please do not use. The field will be removed in Nomad
// 0.13 and is only being kept to allow any references to be removed before
// then.
MBits *int `hcl:"mbits,optional"`
MBits *int `hcl:"mbits,optional"`
CNI *CNIConfig `hcl:"cni,block"`
}

// COMPAT(0.13)
Expand Down
16 changes: 16 additions & 0 deletions client/allocrunner/networking_cni.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,18 @@ const (
ConsulIPTablesConfigEnvVar = "CONSUL_IPTABLES_CONFIG"
)

// Adds user inputted custom CNI args to cniArgs map
func addCustomCNIArgs(networks []*structs.NetworkResource, cniArgs map[string]string) {
for _, net := range networks {
if net.CNI == nil {
continue
}
for k, v := range net.CNI.Args {
cniArgs[k] = v
}
}
}

// Setup calls the CNI plugins with the add action
func (c *cniNetworkConfigurator) Setup(ctx context.Context, alloc *structs.Allocation, spec *drivers.NetworkIsolationSpec) (*structs.AllocNetworkStatus, error) {
if err := c.ensureCNIInitialized(); err != nil {
Expand All @@ -114,6 +126,10 @@ func (c *cniNetworkConfigurator) Setup(ctx context.Context, alloc *structs.Alloc
"IgnoreUnknown": "true",
}

tg := alloc.Job.LookupTaskGroup(alloc.TaskGroup)

addCustomCNIArgs(tg.Networks, cniArgs)

portMaps := getPortMapping(alloc, c.ignorePortMappingHostIP)

tproxyArgs, err := c.setupTransparentProxyArgs(alloc, spec, portMaps)
Expand Down
47 changes: 47 additions & 0 deletions client/allocrunner/networking_cni_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,53 @@ func TestCNI_cniToAllocNet_Invalid(t *testing.T) {
require.Nil(t, allocNet)
}

func TestCNI_addCustomCNIArgs(t *testing.T) {
ci.Parallel(t)
cniArgs := map[string]string{
"default": "yup",
}

networkWithArgs := []*structs.NetworkResource{{
CNI: &structs.CNIConfig{
Args: map[string]string{
"first_arg": "example",
"new_arg": "example_2",
},
},
}}
networkWithoutArgs := []*structs.NetworkResource{{
Mode: "bridge",
}}
testCases := []struct {
name string
network []*structs.NetworkResource
expectMap map[string]string
}{
{
name: "cni args not specified",
network: networkWithoutArgs,
expectMap: map[string]string{
"default": "yup",
},
}, {
name: "cni args specified",
network: networkWithArgs,
expectMap: map[string]string{
"default": "yup",
"first_arg": "example",
"new_arg": "example_2",
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
addCustomCNIArgs(tc.network, cniArgs)
test.Eq(t, tc.expectMap, cniArgs)

})
}
}

func TestCNI_setupTproxyArgs(t *testing.T) {
ci.Parallel(t)

Expand Down
7 changes: 7 additions & 0 deletions client/taskenv/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
// Current interoperable fields:
// - Hostname
// - DNS
// - CNI
func InterpolateNetworks(taskEnv *TaskEnv, networks structs.Networks) structs.Networks {

// Guard against not having a valid taskEnv. This can be the case if the
Expand All @@ -32,6 +33,12 @@ func InterpolateNetworks(taskEnv *TaskEnv, networks structs.Networks) structs.Ne
interpolated[i].DNS.Searches = taskEnv.ParseAndReplace(interpolated[i].DNS.Searches)
interpolated[i].DNS.Options = taskEnv.ParseAndReplace(interpolated[i].DNS.Options)
}
if interpolated[i].CNI != nil {
for k, v := range interpolated[i].CNI.Args {
interpolated[i].CNI.Args[k] = taskEnv.ReplaceEnv(v)

}
}
}

return interpolated
Expand Down
24 changes: 24 additions & 0 deletions client/taskenv/network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,30 @@ func Test_InterpolateNetworks(t *testing.T) {
},
name: "interpolated dns servers",
},
{
inputTaskEnv: testEnv,
inputNetworks: structs.Networks{
{
CNI: &structs.CNIConfig{
Args: map[string]string{
"static": "example",
"second": "${foo}-opt",
},
},
},
},
expectedOutputNetworks: structs.Networks{
{
CNI: &structs.CNIConfig{
Args: map[string]string{
"static": "example",
"second": "bar-opt",
},
},
},
},
name: "interpolated and non-interpolated cni args",
},
}

for _, tc := range testCases {
Expand Down
5 changes: 5 additions & 0 deletions command/agent/job_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,11 @@ func ApiNetworkResourceToStructs(in []*api.NetworkResource) []*structs.NetworkRe
Options: nw.DNS.Options,
}
}
if nw.CNI != nil {
out[i].CNI = &structs.CNIConfig{
Args: nw.CNI.Args,
}
}

if l := len(nw.DynamicPorts); l != 0 {
out[i].DynamicPorts = make([]structs.Port, l)
Expand Down
32 changes: 32 additions & 0 deletions nomad/structs/cni_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package structs

import (
"maps"
)

type CNIConfig struct {
Args map[string]string
}

func (d *CNIConfig) Copy() *CNIConfig {
if d == nil {
return nil
}
newMap := make(map[string]string)
for k, v := range d.Args {
newMap[k] = v
}
return &CNIConfig{
Args: newMap,
}
}

func (d *CNIConfig) Equal(o *CNIConfig) bool {
if d == nil || o == nil {
return d == o
}
return maps.Equal(d.Args, o.Args)
}
28 changes: 28 additions & 0 deletions nomad/structs/cni_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package structs

import (
"github.com/hashicorp/nomad/ci"
"github.com/shoenig/test/must"
"testing"
)

func TestCNIConfig_Equal(t *testing.T) {
ci.Parallel(t)

must.Equal[*CNIConfig](t, nil, nil)
must.NotEqual[*CNIConfig](t, nil, new(CNIConfig))
must.NotEqual[*CNIConfig](t, nil, &CNIConfig{Args: map[string]string{"first": "second"}})

must.StructEqual(t, &CNIConfig{
Args: map[string]string{
"arg": "example_1",
"new_arg": "example_2",
},
}, []must.Tweak[*CNIConfig]{{
Field: "Args",
Apply: func(c *CNIConfig) { c.Args = map[string]string{"different": "arg"} },
}})
}
19 changes: 19 additions & 0 deletions nomad/structs/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -2663,6 +2663,10 @@ func (n *NetworkResource) Diff(other *NetworkResource, contextual bool) *ObjectD
diff.Objects = append(diff.Objects, dnsDiff)
}

if cniDiff := n.CNI.Diff(other.CNI, contextual); cniDiff != nil {
diff.Objects = append(diff.Objects, cniDiff)
}

return diff
}

Expand Down Expand Up @@ -2706,6 +2710,21 @@ func (d *DNSConfig) Diff(other *DNSConfig, contextual bool) *ObjectDiff {
return diff
}

// Diff returns a diff of two CNIConfig structs
func (d *CNIConfig) Diff(other *CNIConfig, contextual bool) *ObjectDiff {
if d == nil {
d = &CNIConfig{}
}
if other == nil {
other = &CNIConfig{}
}
if d.Equal(other) {
return nil
}

return primitiveObjectDiff(d.Args, other.Args, nil, "CNIConfig", contextual)
}

func disconectStrategyDiffs(old, new *DisconnectStrategy, contextual bool) *ObjectDiff {
diff := &ObjectDiff{Type: DiffTypeNone, Name: "Disconnect"}
var oldDisconnectFlat, newDisconnectFlat map[string]string
Expand Down
Loading

0 comments on commit 661011f

Please sign in to comment.