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. // ClusterClientFactory is a factory that creates instances of ClusterClient inventory client.
type ClusterClientFactory struct { type ClusterClientFactory struct {
StatusPolicy StatusPolicy
} }
func (ClusterClientFactory) NewClient(factory cmdutil.Factory) (Client, error) { func (ccf ClusterClientFactory) NewClient(factory cmdutil.Factory) (Client, error) {
return NewClient(factory, WrapInventoryObj, InvInfoToConfigMap) return NewClient(factory, WrapInventoryObj, InvInfoToConfigMap, ccf.StatusPolicy)
} }

View File

@ -54,6 +54,7 @@ type ClusterClient struct {
mapper meta.RESTMapper mapper meta.RESTMapper
InventoryFactoryFunc StorageFactoryFunc InventoryFactoryFunc StorageFactoryFunc
invToUnstructuredFunc ToUnstructuredFunc invToUnstructuredFunc ToUnstructuredFunc
statusPolicy StatusPolicy
} }
var _ Client = &ClusterClient{} var _ Client = &ClusterClient{}
@ -62,7 +63,9 @@ var _ Client = &ClusterClient{}
// Client interface or an error. // Client interface or an error.
func NewClient(factory cmdutil.Factory, func NewClient(factory cmdutil.Factory,
invFunc StorageFactoryFunc, invFunc StorageFactoryFunc,
invToUnstructuredFunc ToUnstructuredFunc) (*ClusterClient, error) { invToUnstructuredFunc ToUnstructuredFunc,
statusPolicy StatusPolicy,
) (*ClusterClient, error) {
dc, err := factory.DynamicClient() dc, err := factory.DynamicClient()
if err != nil { if err != nil {
return nil, err return nil, err
@ -81,6 +84,7 @@ func NewClient(factory cmdutil.Factory,
mapper: mapper, mapper: mapper,
InventoryFactoryFunc: invFunc, InventoryFactoryFunc: invFunc,
invToUnstructuredFunc: invToUnstructuredFunc, invToUnstructuredFunc: invToUnstructuredFunc,
statusPolicy: statusPolicy,
} }
return &clusterClient, nil 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 { 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() { if dryRun.ClientOrServerDryRun() {
klog.V(4).Infof("dry-run update inventory status: not updated") klog.V(4).Infof("dry-run update inventory status: not updated")
return nil return nil

View File

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