Revision d11e4d2612793287ef1c0213c3430c8b58ee24d0 authored by Jarno Rajahalme on 07 June 2024, 07:08:36 UTC, committed by Jarno Rajahalme on 14 June 2024, 10:12:59 UTC
Previously we only reused the DNS proxy port from the datapath. Change to
reuse the old proxy ports for all proxy redirects from datapath, if
possible. This reduces Listener churn on daemonset Envoy on agent
restart.

With this change the listener update in Envoy logs after agent restart
looks like this:

  begin add/update listener: name=cilium-http-egress:13563 hash=11560876577076369351
  duplicate/locked listener 'cilium-http-egress:13563'. no add/update
  lds: add/update listener 'cilium-http-egress:13563' skipped

Signed-off-by: Jarno Rajahalme <jarno@isovalent.com>
1 parent 8149af7
Raw File
map_string_test.go
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium

package command

import (
	"strings"
	"testing"

	"github.com/spf13/viper"
	"github.com/stretchr/testify/assert"
)

func TestGetStringMapString(t *testing.T) {
	expectedResult := map[string]string{
		"k1": "v1",
		"k2": "v2",
	}
	type args struct {
		key   string
		value string
	}
	tests := []struct {
		name    string
		args    args
		want    map[string]string
		wantErr assert.ErrorAssertionFunc
	}{
		{
			name: "valid json format",
			args: args{
				key:   "FOO_BAR",
				value: `{"k1":"v1","k2":"v2"}`,
			},
			want:    expectedResult,
			wantErr: assert.NoError,
		},
		{
			name: "valid empty json",
			args: args{
				key:   "FOO_BAR",
				value: "{}",
			},
			want:    map[string]string{},
			wantErr: assert.NoError,
		},
		{
			name: "invalid json format with extra comma at the end",
			args: args{
				key:   "FOO_BAR",
				value: `{"k1":"v1","k2":"v2",}`,
			},
			want:    map[string]string{},
			wantErr: assertErrorString("invalid character '}' looking for beginning of object key string"),
		},
		{
			name: "valid single kv format",
			args: args{
				key:   "FOO_BAR",
				value: "k1=v1",
			},
			want:    map[string]string{"k1": "v1"},
			wantErr: assert.NoError,
		},
		{
			name: "valid kv format",
			args: args{
				key:   "FOO_BAR",
				value: "k1=v1,k2=v2",
			},
			want:    expectedResult,
			wantErr: assert.NoError,
		},
		{
			name: "valid kv format with @",
			args: args{
				key:   "FOO_BAR",
				value: "k1=v1,k2=test@test.com",
			},
			want: map[string]string{
				"k1": "v1",
				"k2": "test@test.com",
			},
			wantErr: assert.NoError,
		},
		{
			name: "valid kv format with empty value",
			args: args{
				key:   "FOO_BAR",
				value: "k1=,k2=v2",
			},
			want: map[string]string{
				"k1": "",
				"k2": "v2",
			},
			wantErr: assert.NoError,
		},
		{
			name: "valid kv format with a single key and commas in value",
			args: args{
				key:   "API_RATE_LIMIT",
				value: "endpoint-create=rate-limit:10/s,rate-burst:10,parallel-requests:10,auto-adjust:true",
			},
			want: map[string]string{
				"endpoint-create": "rate-limit:10/s,rate-burst:10,parallel-requests:10,auto-adjust:true",
			},
			wantErr: assert.NoError,
		},

		{
			name: "valid kv format with multiple keys with commas in value",
			args: args{
				key:   "API_RATE_LIMIT",
				value: "endpoint-create=rate-limit:10/s,rate-burst:10,parallel-requests:10,auto-adjust:true,endpoint-delete=rate-limit:10/s,rate-burst:10,parallel-requests:10,auto-adjust:true",
			},
			want: map[string]string{
				"endpoint-create": "rate-limit:10/s,rate-burst:10,parallel-requests:10,auto-adjust:true",
				"endpoint-delete": "rate-limit:10/s,rate-burst:10,parallel-requests:10,auto-adjust:true",
			},
			wantErr: assert.NoError,
		},
		{
			name: "another valid kv format with comma in value",
			args: args{
				key:   "AWS_INSTANCE_LIMIT_MAPPING",
				value: "c6a.2xlarge=4,15,15,m4.large=1,5,10",
			},
			want: map[string]string{
				"c6a.2xlarge": "4,15,15",
				"m4.large":    "1,5,10",
			},
			wantErr: assert.NoError,
		},
		{
			name: "valid kv format with forward slash",
			args: args{
				key:   "FOO_BAR",
				value: "kubernetes.io/cluster/piano-eks-general-blue-01=owned,kubernetes.io/role/internal-elb=1",
			},
			want: map[string]string{
				"kubernetes.io/cluster/piano-eks-general-blue-01": "owned",
				"kubernetes.io/role/internal-elb":                 "1",
			},
			wantErr: assert.NoError,
		},
		{
			name: "valid kv format with hyphens",
			args: args{
				key:   "FOO_BAR",
				value: "cluster=my-cluster",
			},
			want: map[string]string{
				"cluster": "my-cluster",
			},
			wantErr: assert.NoError,
		},
		{
			name: "valid kv format with space",
			args: args{
				key:   "FOO_BAR",
				value: "cluster=my cluster",
			},
			want: map[string]string{
				"cluster": "my cluster",
			},
			wantErr: assert.NoError,
		},
		{
			name: "valid kv format from issue #20666",
			args: args{
				key:   "FOO_BAR",
				value: "a=b,c=d,E=F,G=h",
			},
			want: map[string]string{
				"a": "b",
				"c": "d",
				"E": "F",
				"G": "h",
			},
			wantErr: assert.NoError,
		},
		{
			name: "valid kv format for cluster-pool-map",
			args: args{
				key:   "CLUSTER_POOL_MAP",
				value: "mars=ipv4-cidrs:172.16.0.0/16,172.17.0.0/16;ipv4-mask-size:24,jupiter=ipv4-cidrs:192.168.0.0/19;ipv4-mask-size:26",
			},
			want: map[string]string{
				"mars":    "ipv4-cidrs:172.16.0.0/16,172.17.0.0/16;ipv4-mask-size:24",
				"jupiter": "ipv4-cidrs:192.168.0.0/19;ipv4-mask-size:26",
			},
			wantErr: assert.NoError,
		},
		{
			name: "invalid kv format with extra comma",
			args: args{
				key:   "FOO_BAR",
				value: "k1=v1,k2=v2,",
			},
			want:    map[string]string{},
			wantErr: assertErrorString("'k1=v1,k2=v2,' is not formatted as key=value,key1=value1"),
		},
		{
			name: "invalid kv format with extra equal",
			args: args{
				key:   "FOO_BAR",
				value: "k1=v1,k2==v2",
			},
			want:    map[string]string{},
			wantErr: assertErrorString("'k1=v1,k2==v2' is not formatted as key=value,key1=value1"),
		},
		{
			name: "invalid kv format with wrong space in between",
			args: args{
				key:   "FOO_BAR",
				value: "k1=v1, k2=v2",
			},
			want:    map[string]string{},
			wantErr: assertErrorString("'k1=v1, k2=v2' is not formatted as key=value,key1=value1"),
		},
		{
			name: "malformed json format",
			args: args{
				key:   "FOO_BAR",
				value: `{"k1": "v1",=sdlkfj`,
			},
			want:    map[string]string{},
			wantErr: assertErrorString("invalid character '=' looking for beginning of object key string"),
		},
		{
			name: "staring with valid json beginning value e.g. t, f, n, 0, -, \"",
			args: args{
				key:   "FOO_BAR",
				value: "this is a sentence used in test",
			},
			want:    map[string]string{},
			wantErr: assertErrorString("'this is a sentence used in test' is not formatted as key=value,key1=value1"),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			vp := viper.New()
			vp.AutomaticEnv()
			t.Setenv(strings.ToUpper(tt.args.key), tt.args.value)
			v, err := GetStringMapStringE(vp, strings.ToLower(tt.args.key))
			tt.wantErr(t, err)
			assert.Equal(t, tt.want, v)
		})
	}
}

func TestGetStringMapStringConversion(t *testing.T) {
	vp := viper.New()
	vp.Set("foo_bar", struct{}{})
	v, err := GetStringMapStringE(vp, "foo_bar")
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "unable to cast struct {}{} of type struct {} to map[string]string")
	assert.Equal(t, map[string]string{}, v)
}

func Test_isValidKeyValuePair(t *testing.T) {
	type args struct {
		str string
	}
	tests := []struct {
		name string
		args args
		want bool
	}{
		{
			name: "valid format with one pair",
			args: args{
				str: "k1=v1",
			},
			want: true,
		},
		{
			name: "valid format with hyphen in k and v",
			args: args{
				str: "k-1=v-1,k-2=v-2",
			},
			want: true,
		},
		{
			name: "valid format with multiple hyphens",
			args: args{
				str: "Cluster=piano-eks-general-blue-01",
			},
			want: true,
		},
		{
			name: "valid format with colon",
			args: args{
				str: "consul.address=127.0.0.1:8500",
			},
			want: true,
		},
		{
			name: "valid format with forward slash",
			args: args{
				str: "kubernetes.io/cluster/piano-eks-general-blue-01=owned",
			},
			want: true,
		},
		{
			name: "valid format with multiple pairs",
			args: args{
				str: "k1=v1,k2=v2,k3=v3,k4=v4,k4=v4,k4=v4",
			},
			want: true,
		},
		{
			name: "valid format with multiple pairs with commas",
			args: args{
				str: "k1=v,1,k2=v2,,k3=,v3,k4=v,4,k4=v4,k4=v,4",
			},
			want: true,
		},
		{
			name: "empty value",
			args: args{
				str: "",
			},
			want: true,
		},
		{
			name: "space in between",
			args: args{
				str: "k1=v1, k2=v2",
			},
			want: false,
		},
		{
			name: "insufficient value",
			args: args{
				str: "k1=v1,k2,=v2",
			},
			want: false,
		},
		{
			name: "no pair at all",
			args: args{
				str: "here-is-the-test",
			},
			want: false,
		},
		{
			name: "ending with comma",
			args: args{
				str: "k1=v1,k2=v2,",
			},
			want: false,
		},
		{
			name: "ending with equal",
			args: args{
				str: "k1=v1,k2=v2=",
			},
			want: false,
		},
		{
			name: "kv separator as space",
			args: args{
				str: "k1=v1 k2=v2=",
			},
			want: false,
		},
		{
			name: "space in key",
			args: args{
				str: "k1=v1, k2=v2",
			},
			want: false,
		},
		{
			name: "value starts with space",
			args: args{
				str: "k1= v1,k2=v2",
			},
			want: false,
		},
		{
			name: "last value starts with space",
			args: args{
				str: "k1=v1,k2= v2",
			},
			want: false,
		},
		{
			name: "value ends with space",
			args: args{
				str: "k1=v1 ,k2=v2",
			},
			want: false,
		},
		{
			name: "last value ends with space",
			args: args{
				str: "k1=v1,k2=v2 ",
			},
			want: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equalf(t, tt.want, isValidKeyValuePair(tt.args.str), "isValidKeyValuePair(%v)", tt.args.str)
		})
	}
}

func assertErrorString(errString string) assert.ErrorAssertionFunc {
	return func(t assert.TestingT, err error, msgAndArgs ...interface{}) bool {
		return assert.EqualError(t, err, errString, msgAndArgs)
	}
}
back to top