diff --git a/cns/cnireconciler/podinfoprovider_test.go b/cns/cnireconciler/podinfoprovider_test.go index f7bd57b3a4..64149d07a0 100644 --- a/cns/cnireconciler/podinfoprovider_test.go +++ b/cns/cnireconciler/podinfoprovider_test.go @@ -36,6 +36,14 @@ func TestNewCNIPodInfoProvider(t *testing.T) { }, wantErr: false, }, + { + name: "empty CNI response", + exec: newCNIStateFakeExec( + `{}`, + ), + want: map[string]cns.PodInfo{}, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cns/cnireconciler/statefile.go b/cns/cnireconciler/statefile.go new file mode 100644 index 0000000000..1d9b373e27 --- /dev/null +++ b/cns/cnireconciler/statefile.go @@ -0,0 +1,41 @@ +package cnireconciler + +import ( + "encoding/json" + "errors" + "os" + + "github.com/Azure/azure-container-networking/cns/logger" +) + +// WriteObjectToCNIStatefile checks for a file at the CNI statefile path, +// and checks if it is empty. If it is empty, writes an empty JSON object to +// it so older CNI can execute. Does nothing and returns no error if the +// file does not exist. +// +// This is a hack to get older CNI to run when CNS has mounted the statefile +// path, but the statefile wasn't written by CNI yet. Kubelet will stub an +// empty file on the host filesystem, crashing older CNI because it doesn't know +// how to handle empty statefiles. +func WriteObjectToCNIStatefile() error { + filename := "/var/run/azure-vnet.json" + return writeObjectToFile(filename) +} + +func writeObjectToFile(filename string) error { + fi, err := os.Stat(filename) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + return err + } + + if fi.Size() != 0 { + return nil + } + + logger.Printf("Writing {} to CNI statefile") + b, _ := json.Marshal(map[string]string{}) + return os.WriteFile(filename, b, os.FileMode(0666)) +} diff --git a/cns/cnireconciler/statefile_test.go b/cns/cnireconciler/statefile_test.go new file mode 100644 index 0000000000..e53aaeca5c --- /dev/null +++ b/cns/cnireconciler/statefile_test.go @@ -0,0 +1,41 @@ +package cnireconciler + +import ( + "os" + "path" + "testing" + + "github.com/Azure/azure-container-networking/cns/logger" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWriteObjectToFile(t *testing.T) { + name := "testdata/test" + err := os.MkdirAll(path.Dir(name), 0666) + require.NoError(t, err) + + _, err = os.Stat(name) + require.ErrorIs(t, err, os.ErrNotExist) + + // create empty file + _, err = os.Create(name) + require.NoError(t, err) + defer os.Remove(name) + + // check it's empty + fi, _ := os.Stat(name) + assert.Equal(t, fi.Size(), int64(0)) + + // populate + require.NoError(t, writeObjectToFile(name)) + + // read + b, err := os.ReadFile(name) + require.NoError(t, err) + assert.Equal(t, string(b), "{}") +} + +func init() { + logger.InitLogger("testlogs", 0, 0, "./") +} diff --git a/cns/service/main.go b/cns/service/main.go index 17f2f03ef5..8268af0288 100644 --- a/cns/service/main.go +++ b/cns/service/main.go @@ -21,6 +21,7 @@ import ( "github.com/Azure/azure-container-networking/cnm/ipam" "github.com/Azure/azure-container-networking/cnm/network" "github.com/Azure/azure-container-networking/cns" + "github.com/Azure/azure-container-networking/cns/cnireconciler" cni "github.com/Azure/azure-container-networking/cns/cnireconciler" "github.com/Azure/azure-container-networking/cns/cnsclient" "github.com/Azure/azure-container-networking/cns/common" @@ -521,6 +522,13 @@ func main() { // Initialze state in if CNS is running in CRD mode // State must be initialized before we start HTTPRestService if config.ChannelMode == cns.CRD { + // Check the CNI statefile mount, and if the file is empty + // stub an empty JSON object + if err := cnireconciler.WriteObjectToCNIStatefile(); err != nil { + logger.Errorf("Failed to write empty object to CNI state: %v", err) + return + } + // We might be configured to reinitialize state from the CNI instead of the apiserver. // If so, we should check that the the CNI is new enough to support the state commands, // otherwise we fall back to the existing behavior.