argo-cd/controller/cache/cache_test.go
Alexandre Gaudreault 88994ea5cd
feat: add ignoreResourceUpdates to reduce controller CPU usage (#13534) (#13912)
* feat: ignore watched resource update

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* add doc and CLI

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* update doc index

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* add command

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* codegen

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* revert formatting

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* do not skip on health change

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* update doc

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* update logging to use context

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* fix typos. local build broken...

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* change after review

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* manifestHash to string

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* more doc

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* example for argoproj Application

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* add unit test for ignored logs

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* codegen

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* Update docs/operator-manual/reconcile.md

Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* move hash and set log to debug

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* Update util/settings/settings.go

Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* Update util/settings/settings.go

Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* feature flag

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* fix

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* less aggressive managedFields ignore rule

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* Update docs/operator-manual/reconcile.md

Co-authored-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>

* use local settings

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* latest settings

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* safety first

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* since it's behind a feature flag, go aggressive on overrides

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

---------

Signed-off-by: Alexandre Gaudreault <alexandre.gaudreault@logmein.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-06-25 01:32:20 +00:00

328 lines
9.1 KiB
Go

package cache
import (
"errors"
"net"
"net/url"
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/argoproj/gitops-engine/pkg/cache"
"github.com/argoproj/gitops-engine/pkg/cache/mocks"
"github.com/argoproj/gitops-engine/pkg/health"
"github.com/stretchr/testify/mock"
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
type netError string
func (n netError) Error() string { return string(n) }
func (n netError) Timeout() bool { return false }
func (n netError) Temporary() bool { return false }
func TestHandleModEvent_HasChanges(t *testing.T) {
clusterCache := &mocks.ClusterCache{}
clusterCache.On("Invalidate", mock.Anything, mock.Anything).Return(nil).Once()
clusterCache.On("EnsureSynced").Return(nil).Once()
clustersCache := liveStateCache{
clusters: map[string]cache.ClusterCache{
"https://mycluster": clusterCache,
},
}
clustersCache.handleModEvent(&appv1.Cluster{
Server: "https://mycluster",
Config: appv1.ClusterConfig{Username: "foo"},
}, &appv1.Cluster{
Server: "https://mycluster",
Config: appv1.ClusterConfig{Username: "bar"},
Namespaces: []string{"default"},
})
}
func TestHandleModEvent_ClusterExcluded(t *testing.T) {
clusterCache := &mocks.ClusterCache{}
clusterCache.On("Invalidate", mock.Anything, mock.Anything).Return(nil).Once()
clusterCache.On("EnsureSynced").Return(nil).Once()
clustersCache := liveStateCache{
clusters: map[string]cache.ClusterCache{
"https://mycluster": clusterCache,
},
clusterFilter: func(cluster *appv1.Cluster) bool {
return false
},
}
clustersCache.handleModEvent(&appv1.Cluster{
Server: "https://mycluster",
Config: appv1.ClusterConfig{Username: "foo"},
}, &appv1.Cluster{
Server: "https://mycluster",
Config: appv1.ClusterConfig{Username: "bar"},
Namespaces: []string{"default"},
})
assert.Len(t, clustersCache.clusters, 0)
}
func TestHandleModEvent_NoChanges(t *testing.T) {
clusterCache := &mocks.ClusterCache{}
clusterCache.On("Invalidate", mock.Anything).Panic("should not invalidate")
clusterCache.On("EnsureSynced").Return(nil).Panic("should not re-sync")
clustersCache := liveStateCache{
clusters: map[string]cache.ClusterCache{
"https://mycluster": clusterCache,
},
}
clustersCache.handleModEvent(&appv1.Cluster{
Server: "https://mycluster",
Config: appv1.ClusterConfig{Username: "bar"},
}, &appv1.Cluster{
Server: "https://mycluster",
Config: appv1.ClusterConfig{Username: "bar"},
})
}
func TestHandleAddEvent_ClusterExcluded(t *testing.T) {
clustersCache := liveStateCache{
clusters: map[string]cache.ClusterCache{},
clusterFilter: func(cluster *appv1.Cluster) bool {
return false
},
}
clustersCache.handleAddEvent(&appv1.Cluster{
Server: "https://mycluster",
Config: appv1.ClusterConfig{Username: "bar"},
})
assert.Len(t, clustersCache.clusters, 0)
}
func TestIsRetryableError(t *testing.T) {
var (
tlsHandshakeTimeoutErr net.Error = netError("net/http: TLS handshake timeout")
ioTimeoutErr net.Error = netError("i/o timeout")
connectionTimedout net.Error = netError("connection timed out")
connectionReset net.Error = netError("connection reset by peer")
)
t.Run("Nil", func(t *testing.T) {
assert.False(t, isRetryableError(nil))
})
t.Run("ResourceQuotaConflictErr", func(t *testing.T) {
assert.False(t, isRetryableError(apierr.NewConflict(schema.GroupResource{}, "", nil)))
assert.True(t, isRetryableError(apierr.NewConflict(schema.GroupResource{Group: "v1", Resource: "resourcequotas"}, "", nil)))
})
t.Run("ExceededQuotaErr", func(t *testing.T) {
assert.False(t, isRetryableError(apierr.NewForbidden(schema.GroupResource{}, "", nil)))
assert.True(t, isRetryableError(apierr.NewForbidden(schema.GroupResource{Group: "v1", Resource: "pods"}, "", errors.New("exceeded quota"))))
})
t.Run("TooManyRequestsDNS", func(t *testing.T) {
assert.True(t, isRetryableError(apierr.NewTooManyRequests("", 0)))
})
t.Run("DNSError", func(t *testing.T) {
assert.True(t, isRetryableError(&net.DNSError{}))
})
t.Run("OpError", func(t *testing.T) {
assert.True(t, isRetryableError(&net.OpError{}))
})
t.Run("UnknownNetworkError", func(t *testing.T) {
assert.True(t, isRetryableError(net.UnknownNetworkError("")))
})
t.Run("ConnectionClosedErr", func(t *testing.T) {
assert.False(t, isRetryableError(&url.Error{Err: errors.New("")}))
assert.True(t, isRetryableError(&url.Error{Err: errors.New("Connection closed by foreign host")}))
})
t.Run("TLSHandshakeTimeout", func(t *testing.T) {
assert.True(t, isRetryableError(tlsHandshakeTimeoutErr))
})
t.Run("IOHandshakeTimeout", func(t *testing.T) {
assert.True(t, isRetryableError(ioTimeoutErr))
})
t.Run("ConnectionTimeout", func(t *testing.T) {
assert.True(t, isRetryableError(connectionTimedout))
})
t.Run("ConnectionReset", func(t *testing.T) {
assert.True(t, isRetryableError(connectionReset))
})
}
func Test_asResourceNode_owner_refs(t *testing.T) {
resNode := asResourceNode(&cache.Resource{
ResourceVersion: "",
Ref: v1.ObjectReference{
APIVersion: "v1",
},
OwnerRefs: []metav1.OwnerReference{
{
APIVersion: "v1",
Kind: "ConfigMap",
Name: "cm-1",
},
{
APIVersion: "v1",
Kind: "ConfigMap",
Name: "cm-2",
},
},
CreationTimestamp: nil,
Info: nil,
Resource: nil,
})
expected := appv1.ResourceNode{
ResourceRef: appv1.ResourceRef{
Version: "v1",
},
ParentRefs: []appv1.ResourceRef{
{
Group: "",
Kind: "ConfigMap",
Name: "cm-1",
},
{
Group: "",
Kind: "ConfigMap",
Name: "cm-2",
},
},
Info: nil,
NetworkingInfo: nil,
ResourceVersion: "",
Images: nil,
Health: nil,
CreatedAt: nil,
}
assert.Equal(t, expected, resNode)
}
func TestSkipResourceUpdate(t *testing.T) {
var (
hash1_x string = "x"
hash2_y string = "y"
hash3_x string = "x"
)
info := &ResourceInfo{
manifestHash: hash1_x,
Health: &health.HealthStatus{
Status: health.HealthStatusHealthy,
Message: "default",
},
}
t.Run("Nil", func(t *testing.T) {
assert.False(t, skipResourceUpdate(nil, nil))
})
t.Run("From Nil", func(t *testing.T) {
assert.False(t, skipResourceUpdate(nil, info))
})
t.Run("To Nil", func(t *testing.T) {
assert.False(t, skipResourceUpdate(info, nil))
})
t.Run("No hash", func(t *testing.T) {
assert.False(t, skipResourceUpdate(&ResourceInfo{}, &ResourceInfo{}))
})
t.Run("Same hash", func(t *testing.T) {
assert.True(t, skipResourceUpdate(&ResourceInfo{
manifestHash: hash1_x,
}, &ResourceInfo{
manifestHash: hash1_x,
}))
})
t.Run("Same hash value", func(t *testing.T) {
assert.True(t, skipResourceUpdate(&ResourceInfo{
manifestHash: hash1_x,
}, &ResourceInfo{
manifestHash: hash3_x,
}))
})
t.Run("Different hash value", func(t *testing.T) {
assert.False(t, skipResourceUpdate(&ResourceInfo{
manifestHash: hash1_x,
}, &ResourceInfo{
manifestHash: hash2_y,
}))
})
t.Run("Same hash, empty health", func(t *testing.T) {
assert.True(t, skipResourceUpdate(&ResourceInfo{
manifestHash: hash1_x,
Health: &health.HealthStatus{},
}, &ResourceInfo{
manifestHash: hash3_x,
Health: &health.HealthStatus{},
}))
})
t.Run("Same hash, old health", func(t *testing.T) {
assert.False(t, skipResourceUpdate(&ResourceInfo{
manifestHash: hash1_x,
Health: &health.HealthStatus{
Status: health.HealthStatusHealthy},
}, &ResourceInfo{
manifestHash: hash3_x,
Health: nil,
}))
})
t.Run("Same hash, new health", func(t *testing.T) {
assert.False(t, skipResourceUpdate(&ResourceInfo{
manifestHash: hash1_x,
Health: &health.HealthStatus{},
}, &ResourceInfo{
manifestHash: hash3_x,
Health: &health.HealthStatus{
Status: health.HealthStatusHealthy,
},
}))
})
t.Run("Same hash, same health", func(t *testing.T) {
assert.True(t, skipResourceUpdate(&ResourceInfo{
manifestHash: hash1_x,
Health: &health.HealthStatus{
Status: health.HealthStatusHealthy,
Message: "same",
},
}, &ResourceInfo{
manifestHash: hash3_x,
Health: &health.HealthStatus{
Status: health.HealthStatusHealthy,
Message: "same",
},
}))
})
t.Run("Same hash, different health status", func(t *testing.T) {
assert.False(t, skipResourceUpdate(&ResourceInfo{
manifestHash: hash1_x,
Health: &health.HealthStatus{
Status: health.HealthStatusHealthy,
Message: "same",
},
}, &ResourceInfo{
manifestHash: hash3_x,
Health: &health.HealthStatus{
Status: health.HealthStatusDegraded,
Message: "same",
},
}))
})
t.Run("Same hash, different health message", func(t *testing.T) {
assert.True(t, skipResourceUpdate(&ResourceInfo{
manifestHash: hash1_x,
Health: &health.HealthStatus{
Status: health.HealthStatusHealthy,
Message: "same",
},
}, &ResourceInfo{
manifestHash: hash3_x,
Health: &health.HealthStatus{
Status: health.HealthStatusHealthy,
Message: "different",
},
}))
})
}