feat: Add inventory.StatusPolicy

- StatusPolicyNone disables inventory status updates.
- StatusPolicyAll fully enables inventory status updates.
- This allows an opt-out feature for working around the problem
  that adding status can make the inventory larger than the max
  etcd object size, causing the applier to exit without applying
  or pruning anything. With StatusPolicyNone, the user can still
  safely prune objects to make their inventory smaller, and then
  re-enable the status with StatusPolicyAll.
- Note: the default ConfigMap does not currently support status,
  so this only affects custom inventory impls.
This commit is contained in:
Karl Isenberg 2022-03-04 17:30:44 -08:00
parent 16847b444b
commit 2bdc05945d
6 changed files with 96 additions and 35 deletions

View File

@ -16,8 +16,9 @@ type ClientFactory interface {
// ClusterClientFactory is a factory that creates instances of ClusterClient inventory client.
type ClusterClientFactory struct {
StatusPolicy StatusPolicy
}
func (ClusterClientFactory) NewClient(factory cmdutil.Factory) (Client, error) {
return NewClient(factory, WrapInventoryObj, InvInfoToConfigMap)
func (ccf ClusterClientFactory) NewClient(factory cmdutil.Factory) (Client, error) {
return NewClient(factory, WrapInventoryObj, InvInfoToConfigMap, ccf.StatusPolicy)
}

View File

@ -54,6 +54,7 @@ type ClusterClient struct {
mapper meta.RESTMapper
InventoryFactoryFunc StorageFactoryFunc
invToUnstructuredFunc ToUnstructuredFunc
statusPolicy StatusPolicy
}
var _ Client = &ClusterClient{}
@ -62,7 +63,9 @@ var _ Client = &ClusterClient{}
// Client interface or an error.
func NewClient(factory cmdutil.Factory,
invFunc StorageFactoryFunc,
invToUnstructuredFunc ToUnstructuredFunc) (*ClusterClient, error) {
invToUnstructuredFunc ToUnstructuredFunc,
statusPolicy StatusPolicy,
) (*ClusterClient, error) {
dc, err := factory.DynamicClient()
if err != nil {
return nil, err
@ -81,6 +84,7 @@ func NewClient(factory cmdutil.Factory,
mapper: mapper,
InventoryFactoryFunc: invFunc,
invToUnstructuredFunc: invToUnstructuredFunc,
statusPolicy: statusPolicy,
}
return &clusterClient, nil
}
@ -451,6 +455,10 @@ func (cic *ClusterClient) getMapping(obj *unstructured.Unstructured) (*meta.REST
}
func (cic *ClusterClient) updateStatus(obj *unstructured.Unstructured, dryRun common.DryRunStrategy) error {
if cic.statusPolicy != StatusPolicyAll {
klog.V(4).Infof("inventory status update skipped (StatusPolicy: %s)", cic.statusPolicy)
return nil
}
if dryRun.ClientOrServerDryRun() {
klog.V(4).Infof("dry-run update inventory status: not updated")
return nil

View File

@ -39,10 +39,11 @@ func podData(name string) map[string]string {
func TestGetClusterInventoryInfo(t *testing.T) {
tests := map[string]struct {
inv Info
localObjs object.ObjMetadataSet
objStatus []actuation.ObjectStatus
isError bool
statusPolicy StatusPolicy
inv Info
localObjs object.ObjMetadataSet
objStatus []actuation.ObjectStatus
isError bool
}{
"Nil local inventory object is an error": {
inv: nil,
@ -83,7 +84,7 @@ func TestGetClusterInventoryInfo(t *testing.T) {
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
invClient, err := NewClient(tf,
WrapInventoryObj, InvInfoToConfigMap)
WrapInventoryObj, InvInfoToConfigMap, tc.statusPolicy)
require.NoError(t, err)
var inv *unstructured.Unstructured
@ -116,11 +117,12 @@ func TestGetClusterInventoryInfo(t *testing.T) {
func TestMerge(t *testing.T) {
tests := map[string]struct {
localInv Info
localObjs object.ObjMetadataSet
clusterObjs object.ObjMetadataSet
pruneObjs object.ObjMetadataSet
isError bool
statusPolicy StatusPolicy
localInv Info
localObjs object.ObjMetadataSet
clusterObjs object.ObjMetadataSet
pruneObjs object.ObjMetadataSet
isError bool
}{
"Nil local inventory object is error": {
localInv: nil,
@ -188,7 +190,7 @@ func TestMerge(t *testing.T) {
tf.FakeDynamicClient.PrependReactor("list", "configmaps", toReactionFunc(tc.clusterObjs))
// Create the local inventory object storing "tc.localObjs"
invClient, err := NewClient(tf,
WrapInventoryObj, InvInfoToConfigMap)
WrapInventoryObj, InvInfoToConfigMap, tc.statusPolicy)
require.NoError(t, err)
// Call "Merge" to create the union of clusterObjs and localObjs.
@ -212,10 +214,11 @@ func TestMerge(t *testing.T) {
func TestCreateInventory(t *testing.T) {
tests := map[string]struct {
inv Info
localObjs object.ObjMetadataSet
error string
objStatus []actuation.ObjectStatus
statusPolicy StatusPolicy
inv Info
localObjs object.ObjMetadataSet
error string
objStatus []actuation.ObjectStatus
}{
"Nil local inventory object is an error": {
inv: nil,
@ -260,7 +263,7 @@ func TestCreateInventory(t *testing.T) {
})
invClient, err := NewClient(tf,
WrapInventoryObj, InvInfoToConfigMap)
WrapInventoryObj, InvInfoToConfigMap, tc.statusPolicy)
require.NoError(t, err)
inv := invClient.invToUnstructuredFunc(tc.inv)
if inv != nil {
@ -288,10 +291,11 @@ func TestCreateInventory(t *testing.T) {
func TestReplace(t *testing.T) {
tests := map[string]struct {
localObjs object.ObjMetadataSet
clusterObjs object.ObjMetadataSet
objStatus []actuation.ObjectStatus
data map[string]string
statusPolicy StatusPolicy
localObjs object.ObjMetadataSet
clusterObjs object.ObjMetadataSet
objStatus []actuation.ObjectStatus
data map[string]string
}{
"Cluster and local inventories empty": {
localObjs: object.ObjMetadataSet{},
@ -336,7 +340,8 @@ func TestReplace(t *testing.T) {
defer tf.Cleanup()
// Client and server dry-run do not throw errors.
invClient, err := NewClient(tf, WrapInventoryObj, InvInfoToConfigMap)
invClient, err := NewClient(tf,
WrapInventoryObj, InvInfoToConfigMap, StatusPolicyAll)
require.NoError(t, err)
err = invClient.Replace(copyInventory(), object.ObjMetadataSet{}, nil, common.DryRunClient)
if err != nil {
@ -351,7 +356,7 @@ func TestReplace(t *testing.T) {
t.Run(name, func(t *testing.T) {
// Create inventory client, and store the cluster objs in the inventory object.
invClient, err := NewClient(tf,
WrapInventoryObj, InvInfoToConfigMap)
WrapInventoryObj, InvInfoToConfigMap, tc.statusPolicy)
require.NoError(t, err)
wrappedInv := invClient.InventoryFactoryFunc(inventoryObj)
if err := wrappedInv.Store(tc.clusterObjs, tc.objStatus); err != nil {
@ -388,9 +393,10 @@ func TestReplace(t *testing.T) {
func TestGetClusterObjs(t *testing.T) {
tests := map[string]struct {
localInv Info
clusterObjs object.ObjMetadataSet
isError bool
statusPolicy StatusPolicy
localInv Info
clusterObjs object.ObjMetadataSet
isError bool
}{
"Nil cluster inventory is error": {
localInv: nil,
@ -421,7 +427,7 @@ func TestGetClusterObjs(t *testing.T) {
tf.FakeDynamicClient.PrependReactor("list", "configmaps", toReactionFunc(tc.clusterObjs))
invClient, err := NewClient(tf,
WrapInventoryObj, InvInfoToConfigMap)
WrapInventoryObj, InvInfoToConfigMap, tc.statusPolicy)
require.NoError(t, err)
clusterObjs, err := invClient.GetClusterObjs(tc.localInv)
if tc.isError {
@ -442,9 +448,10 @@ func TestGetClusterObjs(t *testing.T) {
func TestDeleteInventoryObj(t *testing.T) {
tests := map[string]struct {
inv Info
localObjs object.ObjMetadataSet
objStatus []actuation.ObjectStatus
statusPolicy StatusPolicy
inv Info
localObjs object.ObjMetadataSet
objStatus []actuation.ObjectStatus
}{
"Nil local inventory object is an error": {
inv: nil,
@ -483,7 +490,7 @@ func TestDeleteInventoryObj(t *testing.T) {
defer tf.Cleanup()
invClient, err := NewClient(tf,
WrapInventoryObj, InvInfoToConfigMap)
WrapInventoryObj, InvInfoToConfigMap, tc.statusPolicy)
require.NoError(t, err)
inv := invClient.invToUnstructuredFunc(tc.inv)
if inv != nil {
@ -500,6 +507,7 @@ func TestDeleteInventoryObj(t *testing.T) {
func TestApplyInventoryNamespace(t *testing.T) {
testCases := map[string]struct {
statusPolicy StatusPolicy
namespace *unstructured.Unstructured
dryRunStrategy common.DryRunStrategy
reactorError error
@ -529,7 +537,7 @@ func TestApplyInventoryNamespace(t *testing.T) {
})
invClient, err := NewClient(tf,
WrapInventoryObj, InvInfoToConfigMap)
WrapInventoryObj, InvInfoToConfigMap, tc.statusPolicy)
require.NoError(t, err)
err = invClient.ApplyInventoryNamespace(tc.namespace, tc.dryRunStrategy)
assert.NoError(t, err)

View File

@ -0,0 +1,18 @@
// Copyright 2022 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package inventory
// StatusPolicy specifies whether the inventory client should apply status to
// the inventory object. The status contains the actuation and reconcile stauts
// of each object in the inventory.
//go:generate stringer -type=StatusPolicy -linecomment
type StatusPolicy int
const (
// StatusPolicyNone disables inventory status updates.
StatusPolicyNone StatusPolicy = iota // None
// StatusPolicyAll fully enables inventory status updates.
StatusPolicyAll // All
)

View File

@ -0,0 +1,24 @@
// Code generated by "stringer -type=StatusPolicy -linecomment"; DO NOT EDIT.
package inventory
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[StatusPolicyNone-0]
_ = x[StatusPolicyAll-1]
}
const _StatusPolicy_name = "NoneAll"
var _StatusPolicy_index = [...]uint8{0, 4, 7}
func (i StatusPolicy) String() string {
if i < 0 || i >= StatusPolicy(len(_StatusPolicy_index)-1) {
return "StatusPolicy(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _StatusPolicy_name[_StatusPolicy_index[i]:_StatusPolicy_index[i+1]]
}

View File

@ -81,7 +81,9 @@ type CustomClientFactory struct {
}
func (CustomClientFactory) NewClient(factory util.Factory) (inventory.Client, error) {
return inventory.NewClient(factory, WrapInventoryObj, invToUnstructuredFunc)
// TODO: add status to custom inventory crd and enable StatusPolicyAll
return inventory.NewClient(factory,
WrapInventoryObj, invToUnstructuredFunc, inventory.StatusPolicyNone)
}
func invToUnstructuredFunc(inv inventory.Info) *unstructured.Unstructured {