mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
Merge pull request #116330 from SataQiu/clean-kubelet-20230307
Followup 112643: remove residual code associated with DynamicKubeletConfig
This commit is contained in:
commit
e192a7dbcc
@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package checkpoint
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// configMapPayload implements Payload, backed by a v1/ConfigMap config source object
|
|
||||||
type configMapPayload struct {
|
|
||||||
cm *apiv1.ConfigMap
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Payload = (*configMapPayload)(nil)
|
|
||||||
|
|
||||||
// NewConfigMapPayload constructs a Payload backed by a ConfigMap, which must have a non-empty UID
|
|
||||||
func NewConfigMapPayload(cm *apiv1.ConfigMap) (Payload, error) {
|
|
||||||
if cm == nil {
|
|
||||||
return nil, fmt.Errorf("ConfigMap must be non-nil")
|
|
||||||
} else if cm.ObjectMeta.UID == "" {
|
|
||||||
return nil, fmt.Errorf("ConfigMap must have a non-empty UID")
|
|
||||||
} else if cm.ObjectMeta.ResourceVersion == "" {
|
|
||||||
return nil, fmt.Errorf("ConfigMap must have a non-empty ResourceVersion")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &configMapPayload{cm}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *configMapPayload) UID() string {
|
|
||||||
return string(p.cm.UID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *configMapPayload) ResourceVersion() string {
|
|
||||||
return p.cm.ResourceVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *configMapPayload) Files() map[string]string {
|
|
||||||
return p.cm.Data
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *configMapPayload) object() interface{} {
|
|
||||||
return p.cm
|
|
||||||
}
|
|
@ -1,148 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package checkpoint
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
utiltest "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/test"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewConfigMapPayload(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
cm *apiv1.ConfigMap
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "nil",
|
|
||||||
cm: nil,
|
|
||||||
err: "ConfigMap must be non-nil",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "missing uid",
|
|
||||||
cm: &apiv1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "name",
|
|
||||||
ResourceVersion: "rv",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err: "ConfigMap must have a non-empty UID",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "missing resourceVersion",
|
|
||||||
cm: &apiv1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "name",
|
|
||||||
UID: "uid",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err: "ConfigMap must have a non-empty ResourceVersion",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "populated v1/ConfigMap",
|
|
||||||
cm: &apiv1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "name",
|
|
||||||
UID: "uid",
|
|
||||||
ResourceVersion: "rv",
|
|
||||||
},
|
|
||||||
Data: map[string]string{
|
|
||||||
"key1": "value1",
|
|
||||||
"key2": "value2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
err: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(c.desc, func(t *testing.T) {
|
|
||||||
payload, err := NewConfigMapPayload(c.cm)
|
|
||||||
utiltest.ExpectError(t, err, c.err)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// underlying object should match the object passed in
|
|
||||||
if !apiequality.Semantic.DeepEqual(c.cm, payload.object()) {
|
|
||||||
t.Errorf("expect %s but got %s", spew.Sdump(c.cm), spew.Sdump(payload))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigMapPayloadUID(t *testing.T) {
|
|
||||||
const expect = "uid"
|
|
||||||
payload, err := NewConfigMapPayload(&apiv1.ConfigMap{ObjectMeta: metav1.ObjectMeta{UID: expect, ResourceVersion: "rv"}})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing payload: %v", err)
|
|
||||||
}
|
|
||||||
uid := payload.UID()
|
|
||||||
if expect != uid {
|
|
||||||
t.Errorf("expect %q, but got %q", expect, uid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigMapPayloadResourceVersion(t *testing.T) {
|
|
||||||
const expect = "rv"
|
|
||||||
payload, err := NewConfigMapPayload(&apiv1.ConfigMap{ObjectMeta: metav1.ObjectMeta{UID: "uid", ResourceVersion: expect}})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing payload: %v", err)
|
|
||||||
}
|
|
||||||
resourceVersion := payload.ResourceVersion()
|
|
||||||
if expect != resourceVersion {
|
|
||||||
t.Errorf("expect %q, but got %q", expect, resourceVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigMapPayloadFiles(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
data map[string]string
|
|
||||||
expect map[string]string
|
|
||||||
}{
|
|
||||||
{"nil", nil, nil},
|
|
||||||
{"empty", map[string]string{}, map[string]string{}},
|
|
||||||
{"populated",
|
|
||||||
map[string]string{
|
|
||||||
"foo": "1",
|
|
||||||
"bar": "2",
|
|
||||||
},
|
|
||||||
map[string]string{
|
|
||||||
"foo": "1",
|
|
||||||
"bar": "2",
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(c.desc, func(t *testing.T) {
|
|
||||||
payload, err := NewConfigMapPayload(&apiv1.ConfigMap{ObjectMeta: metav1.ObjectMeta{UID: "uid", ResourceVersion: "rv"}, Data: c.data})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing payload: %v", err)
|
|
||||||
}
|
|
||||||
files := payload.Files()
|
|
||||||
if !reflect.DeepEqual(c.expect, files) {
|
|
||||||
t.Errorf("expected %v, but got %v", c.expect, files)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,262 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package checkpoint
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/client-go/tools/cache"
|
|
||||||
kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1"
|
|
||||||
kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config"
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/apis/config/scheme"
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/status"
|
|
||||||
utilcodec "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/codec"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Payload represents a local copy of a config source (payload) object
|
|
||||||
type Payload interface {
|
|
||||||
// UID returns a globally unique (space and time) identifier for the payload.
|
|
||||||
// The return value is guaranteed non-empty.
|
|
||||||
UID() string
|
|
||||||
|
|
||||||
// ResourceVersion returns a resource version for the payload.
|
|
||||||
// The return value is guaranteed non-empty.
|
|
||||||
ResourceVersion() string
|
|
||||||
|
|
||||||
// Files returns a map of filenames to file contents.
|
|
||||||
Files() map[string]string
|
|
||||||
|
|
||||||
// object returns the underlying checkpointed object.
|
|
||||||
object() interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoteConfigSource represents a remote config source object that can be downloaded as a Checkpoint
|
|
||||||
type RemoteConfigSource interface {
|
|
||||||
// KubeletFilename returns the name of the Kubelet config file as it should appear in the keys of Payload.Files()
|
|
||||||
KubeletFilename() string
|
|
||||||
|
|
||||||
// APIPath returns the API path to the remote resource.
|
|
||||||
APIPath() string
|
|
||||||
|
|
||||||
// UID returns the globally unique identifier for the most recently downloaded payload targeted by the source.
|
|
||||||
UID() string
|
|
||||||
|
|
||||||
// ResourceVersion returns the resource version of the most recently downloaded payload targeted by the source.
|
|
||||||
ResourceVersion() string
|
|
||||||
|
|
||||||
// Download downloads the remote config source's target object and returns a Payload backed by the object,
|
|
||||||
// or a sanitized failure reason and error if the download fails.
|
|
||||||
// Download takes an optional store as an argument. If provided, Download will check this store for the
|
|
||||||
// target object prior to contacting the API server.
|
|
||||||
// Download updates the local UID and ResourceVersion tracked by this source, based on the downloaded payload.
|
|
||||||
Download(client clientset.Interface, store cache.Store) (Payload, string, error)
|
|
||||||
|
|
||||||
// Informer returns an informer that can be used to detect changes to the remote config source
|
|
||||||
Informer(client clientset.Interface, handler cache.ResourceEventHandlerFuncs) cache.SharedInformer
|
|
||||||
|
|
||||||
// Encode returns a []byte representation of the object behind the RemoteConfigSource
|
|
||||||
Encode() ([]byte, error)
|
|
||||||
|
|
||||||
// NodeConfigSource returns a copy of the underlying apiv1.NodeConfigSource object.
|
|
||||||
// All RemoteConfigSources are expected to be backed by a NodeConfigSource,
|
|
||||||
// though the convenience methods on the interface will target the source
|
|
||||||
// type that was detected in a call to NewRemoteConfigSource.
|
|
||||||
NodeConfigSource() *apiv1.NodeConfigSource
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRemoteConfigSource constructs a RemoteConfigSource from a v1/NodeConfigSource object
|
|
||||||
// You should only call this with a non-nil config source.
|
|
||||||
// Note that the API server validates Node.Spec.ConfigSource.
|
|
||||||
func NewRemoteConfigSource(source *apiv1.NodeConfigSource) (RemoteConfigSource, string, error) {
|
|
||||||
// NOTE: Even though the API server validates the config, we check whether all *known* fields are
|
|
||||||
// nil here, so that if a new API server allows a new config source type, old clients can send
|
|
||||||
// an error message rather than crashing due to a nil pointer dereference.
|
|
||||||
|
|
||||||
// Exactly one reference subfield of the config source must be non-nil.
|
|
||||||
// Currently ConfigMap is the only reference subfield.
|
|
||||||
if source.ConfigMap == nil {
|
|
||||||
return nil, status.AllNilSubfieldsError, fmt.Errorf("%s, NodeConfigSource was: %#v", status.AllNilSubfieldsError, source)
|
|
||||||
}
|
|
||||||
return &remoteConfigMap{source}, "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeRemoteConfigSource is a helper for using the apimachinery to decode serialized RemoteConfigSources;
|
|
||||||
// e.g. the metadata stored by checkpoint/store/fsstore.go
|
|
||||||
func DecodeRemoteConfigSource(data []byte) (RemoteConfigSource, error) {
|
|
||||||
// Decode the remote config source. We want this to be non-strict
|
|
||||||
// so we don't error out on newer API keys.
|
|
||||||
_, codecs, err := scheme.NewSchemeAndCodecs(serializer.DisableStrict)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
obj, err := runtime.Decode(codecs.UniversalDecoder(), data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode, error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for now we assume we are trying to load an kubeletconfigv1beta1.SerializedNodeConfigSource,
|
|
||||||
// this may need to be extended if e.g. a new version of the api is born
|
|
||||||
cs, ok := obj.(*kubeletconfiginternal.SerializedNodeConfigSource)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("failed to cast decoded remote config source to *k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig.SerializedNodeConfigSource")
|
|
||||||
}
|
|
||||||
|
|
||||||
// we use the v1.NodeConfigSource type on internal and external, so no need to convert to external here
|
|
||||||
source, _, err := NewRemoteConfigSource(&cs.Source)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return source, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EqualRemoteConfigSources is a helper for comparing remote config sources by
|
|
||||||
// comparing the underlying API objects for semantic equality.
|
|
||||||
func EqualRemoteConfigSources(a, b RemoteConfigSource) bool {
|
|
||||||
if a != nil && b != nil {
|
|
||||||
return apiequality.Semantic.DeepEqual(a.NodeConfigSource(), b.NodeConfigSource())
|
|
||||||
}
|
|
||||||
return a == b
|
|
||||||
}
|
|
||||||
|
|
||||||
// remoteConfigMap implements RemoteConfigSource for v1/ConfigMap config sources
|
|
||||||
type remoteConfigMap struct {
|
|
||||||
source *apiv1.NodeConfigSource
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ RemoteConfigSource = (*remoteConfigMap)(nil)
|
|
||||||
|
|
||||||
func (r *remoteConfigMap) KubeletFilename() string {
|
|
||||||
return r.source.ConfigMap.KubeletConfigKey
|
|
||||||
}
|
|
||||||
|
|
||||||
const configMapAPIPathFmt = "/api/v1/namespaces/%s/configmaps/%s"
|
|
||||||
|
|
||||||
func (r *remoteConfigMap) APIPath() string {
|
|
||||||
ref := r.source.ConfigMap
|
|
||||||
return fmt.Sprintf(configMapAPIPathFmt, ref.Namespace, ref.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *remoteConfigMap) UID() string {
|
|
||||||
return string(r.source.ConfigMap.UID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *remoteConfigMap) ResourceVersion() string {
|
|
||||||
return r.source.ConfigMap.ResourceVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *remoteConfigMap) Download(client clientset.Interface, store cache.Store) (Payload, string, error) {
|
|
||||||
var (
|
|
||||||
cm *apiv1.ConfigMap
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
// check the in-memory store for the ConfigMap, so we can skip unnecessary downloads
|
|
||||||
if store != nil {
|
|
||||||
klog.InfoS("Kubelet config controller checking in-memory store for remoteConfigMap", "apiPath", r.APIPath())
|
|
||||||
cm, err = getConfigMapFromStore(store, r.source.ConfigMap.Namespace, r.source.ConfigMap.Name)
|
|
||||||
if err != nil {
|
|
||||||
// just log the error, we'll attempt a direct download instead
|
|
||||||
klog.ErrorS(err, "Kubelet config controller failed to check in-memory store for remoteConfigMap", "apiPath", r.APIPath())
|
|
||||||
} else if cm != nil {
|
|
||||||
klog.InfoS("Kubelet config controller found remoteConfigMap in in-memory store", "apiPath", r.APIPath(), "configMapUID", cm.UID, "resourceVersion", cm.ResourceVersion)
|
|
||||||
} else {
|
|
||||||
klog.InfoS("Kubelet config controller did not find remoteConfigMap in in-memory store", "apiPath", r.APIPath())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if we didn't find the ConfigMap in the in-memory store, download it from the API server
|
|
||||||
if cm == nil {
|
|
||||||
klog.InfoS("Kubelet config controller attempting to download remoteConfigMap", "apiPath", r.APIPath())
|
|
||||||
cm, err = client.CoreV1().ConfigMaps(r.source.ConfigMap.Namespace).Get(context.TODO(), r.source.ConfigMap.Name, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, status.DownloadError, fmt.Errorf("%s, error: %v", status.DownloadError, err)
|
|
||||||
}
|
|
||||||
klog.InfoS("Kubelet config controller successfully downloaded remoteConfigMap", "apiPath", r.APIPath(), "configMapUID", cm.UID, "resourceVersion", cm.ResourceVersion)
|
|
||||||
} // Assert: Now we have a non-nil ConfigMap
|
|
||||||
// construct Payload from the ConfigMap
|
|
||||||
payload, err := NewConfigMapPayload(cm)
|
|
||||||
if err != nil {
|
|
||||||
// We only expect an error here if ObjectMeta is lacking UID or ResourceVersion. This should
|
|
||||||
// never happen on objects in the informer's store, or objects downloaded from the API server
|
|
||||||
// directly, so we report InternalError.
|
|
||||||
return nil, status.InternalError, fmt.Errorf("%s, error: %v", status.InternalError, err)
|
|
||||||
}
|
|
||||||
// update internal UID and ResourceVersion based on latest ConfigMap
|
|
||||||
r.source.ConfigMap.UID = cm.UID
|
|
||||||
r.source.ConfigMap.ResourceVersion = cm.ResourceVersion
|
|
||||||
return payload, "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *remoteConfigMap) Informer(client clientset.Interface, handler cache.ResourceEventHandlerFuncs) cache.SharedInformer {
|
|
||||||
// select ConfigMap by name
|
|
||||||
fieldSelector := fields.OneTermEqualSelector("metadata.name", r.source.ConfigMap.Name)
|
|
||||||
|
|
||||||
// add some randomness to resync period, which can help avoid controllers falling into lock-step
|
|
||||||
minResyncPeriod := 15 * time.Minute
|
|
||||||
factor := rand.Float64() + 1
|
|
||||||
resyncPeriod := time.Duration(float64(minResyncPeriod.Nanoseconds()) * factor)
|
|
||||||
|
|
||||||
lw := cache.NewListWatchFromClient(client.CoreV1().RESTClient(), "configmaps", r.source.ConfigMap.Namespace, fieldSelector)
|
|
||||||
|
|
||||||
informer := cache.NewSharedInformer(lw, &apiv1.ConfigMap{}, resyncPeriod)
|
|
||||||
informer.AddEventHandler(handler)
|
|
||||||
|
|
||||||
return informer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *remoteConfigMap) Encode() ([]byte, error) {
|
|
||||||
encoder, err := utilcodec.NewKubeletconfigYAMLEncoder(kubeletconfigv1beta1.SchemeGroupVersion)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := runtime.Encode(encoder, &kubeletconfigv1beta1.SerializedNodeConfigSource{Source: *r.source})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *remoteConfigMap) NodeConfigSource() *apiv1.NodeConfigSource {
|
|
||||||
return r.source.DeepCopy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getConfigMapFromStore(store cache.Store, namespace, name string) (*apiv1.ConfigMap, error) {
|
|
||||||
key := fmt.Sprintf("%s/%s", namespace, name)
|
|
||||||
obj, ok, err := store.GetByKey(key)
|
|
||||||
if err != nil || !ok {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cm, ok := obj.(*apiv1.ConfigMap)
|
|
||||||
if !ok {
|
|
||||||
err := fmt.Errorf("failed to cast object %s from informer's store to ConfigMap", key)
|
|
||||||
klog.ErrorS(err, "Kubelet config controller")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return cm, nil
|
|
||||||
}
|
|
@ -1,242 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package checkpoint
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
fakeclient "k8s.io/client-go/kubernetes/fake"
|
|
||||||
"k8s.io/client-go/tools/cache"
|
|
||||||
utiltest "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/test"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewRemoteConfigSource(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
source *apiv1.NodeConfigSource
|
|
||||||
expect RemoteConfigSource
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "all NodeConfigSource subfields nil",
|
|
||||||
source: &apiv1.NodeConfigSource{},
|
|
||||||
expect: nil,
|
|
||||||
err: "exactly one subfield must be non-nil",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "ConfigMap: valid reference",
|
|
||||||
source: &apiv1.NodeConfigSource{
|
|
||||||
ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
UID: "uid",
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}},
|
|
||||||
expect: &remoteConfigMap{&apiv1.NodeConfigSource{
|
|
||||||
ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
UID: "uid",
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}}},
|
|
||||||
err: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(c.desc, func(t *testing.T) {
|
|
||||||
source, _, err := NewRemoteConfigSource(c.source)
|
|
||||||
utiltest.ExpectError(t, err, c.err)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// underlying object should match the object passed in
|
|
||||||
if !apiequality.Semantic.DeepEqual(c.expect.NodeConfigSource(), source.NodeConfigSource()) {
|
|
||||||
t.Errorf("case %q, expect RemoteConfigSource %s but got %s", c.desc, spew.Sdump(c.expect), spew.Sdump(source))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoteConfigMapUID(t *testing.T) {
|
|
||||||
const expect = "uid"
|
|
||||||
source, _, err := NewRemoteConfigSource(&apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
UID: expect,
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing remote config source: %v", err)
|
|
||||||
}
|
|
||||||
uid := source.UID()
|
|
||||||
if expect != uid {
|
|
||||||
t.Errorf("expect %q, but got %q", expect, uid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoteConfigMapAPIPath(t *testing.T) {
|
|
||||||
const (
|
|
||||||
name = "name"
|
|
||||||
namespace = "namespace"
|
|
||||||
)
|
|
||||||
source, _, err := NewRemoteConfigSource(&apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: name,
|
|
||||||
Namespace: namespace,
|
|
||||||
UID: "uid",
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing remote config source: %v", err)
|
|
||||||
}
|
|
||||||
expect := fmt.Sprintf(configMapAPIPathFmt, namespace, name)
|
|
||||||
path := source.APIPath()
|
|
||||||
|
|
||||||
if expect != path {
|
|
||||||
t.Errorf("expect %q, but got %q", expect, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemoteConfigMapDownload(t *testing.T) {
|
|
||||||
cm := &apiv1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
UID: "uid",
|
|
||||||
ResourceVersion: "1",
|
|
||||||
}}
|
|
||||||
|
|
||||||
source := &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}}
|
|
||||||
|
|
||||||
expectPayload, err := NewConfigMapPayload(cm)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing payload: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
missingStore := cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc)
|
|
||||||
hasStore := cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc)
|
|
||||||
if err := hasStore.Add(cm); err != nil {
|
|
||||||
t.Fatalf("unexpected error constructing hasStore")
|
|
||||||
}
|
|
||||||
|
|
||||||
missingClient := fakeclient.NewSimpleClientset()
|
|
||||||
hasClient := fakeclient.NewSimpleClientset(cm)
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
client clientset.Interface
|
|
||||||
store cache.Store
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "nil store, object does not exist in API server",
|
|
||||||
client: missingClient,
|
|
||||||
err: "not found",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "nil store, object exists in API server",
|
|
||||||
client: hasClient,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "object exists in store and API server",
|
|
||||||
store: hasStore,
|
|
||||||
client: hasClient,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "object exists in store, but does not exist in API server",
|
|
||||||
store: hasStore,
|
|
||||||
client: missingClient,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "object does not exist in store, but exists in API server",
|
|
||||||
store: missingStore,
|
|
||||||
client: hasClient,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "object does not exist in store or API server",
|
|
||||||
client: missingClient,
|
|
||||||
store: missingStore,
|
|
||||||
err: "not found",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(c.desc, func(t *testing.T) {
|
|
||||||
// deep copy so we can always check the UID/ResourceVersion are set after Download
|
|
||||||
s, _, err := NewRemoteConfigSource(source.DeepCopy())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing remote config source %v", err)
|
|
||||||
}
|
|
||||||
// attempt download
|
|
||||||
p, _, err := s.Download(c.client, c.store)
|
|
||||||
utiltest.ExpectError(t, err, c.err)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// downloaded object should match the expected
|
|
||||||
if !apiequality.Semantic.DeepEqual(expectPayload.object(), p.object()) {
|
|
||||||
t.Errorf("expect Checkpoint %s but got %s", spew.Sdump(expectPayload), spew.Sdump(p))
|
|
||||||
}
|
|
||||||
// source UID and ResourceVersion should be updated by Download
|
|
||||||
if p.UID() != s.UID() {
|
|
||||||
t.Errorf("expect UID to be updated by Download to match payload: %s, but got source UID: %s", p.UID(), s.UID())
|
|
||||||
}
|
|
||||||
if p.ResourceVersion() != s.ResourceVersion() {
|
|
||||||
t.Errorf("expect ResourceVersion to be updated by Download to match payload: %s, but got source ResourceVersion: %s", p.ResourceVersion(), s.ResourceVersion())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEqualRemoteConfigSources(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
a RemoteConfigSource
|
|
||||||
b RemoteConfigSource
|
|
||||||
expect bool
|
|
||||||
}{
|
|
||||||
{"both nil", nil, nil, true},
|
|
||||||
{"a nil", nil, &remoteConfigMap{}, false},
|
|
||||||
{"b nil", &remoteConfigMap{}, nil, false},
|
|
||||||
{"neither nil, equal", &remoteConfigMap{}, &remoteConfigMap{}, true},
|
|
||||||
{
|
|
||||||
desc: "neither nil, not equal",
|
|
||||||
a: &remoteConfigMap{&apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{Name: "a"}}},
|
|
||||||
b: &remoteConfigMap{&apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{KubeletConfigKey: "kubelet"}}},
|
|
||||||
expect: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(c.desc, func(t *testing.T) {
|
|
||||||
if EqualRemoteConfigSources(c.a, c.b) != c.expect {
|
|
||||||
t.Errorf("expected EqualRemoteConfigSources to return %t, but got %t", c.expect, !c.expect)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint"
|
|
||||||
)
|
|
||||||
|
|
||||||
// so far only implements Assigned(), LastKnownGood(), SetAssigned(), and SetLastKnownGood()
|
|
||||||
type fakeStore struct {
|
|
||||||
assigned checkpoint.RemoteConfigSource
|
|
||||||
lastKnownGood checkpoint.RemoteConfigSource
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Store = (*fakeStore)(nil)
|
|
||||||
|
|
||||||
func (s *fakeStore) Initialize() error {
|
|
||||||
return fmt.Errorf("Initialize method not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fakeStore) Exists(source checkpoint.RemoteConfigSource) (bool, error) {
|
|
||||||
return false, fmt.Errorf("Exists method not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fakeStore) Save(c checkpoint.Payload) error {
|
|
||||||
return fmt.Errorf("Save method not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fakeStore) Load(source checkpoint.RemoteConfigSource) (*kubeletconfig.KubeletConfiguration, error) {
|
|
||||||
return nil, fmt.Errorf("Load method not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fakeStore) AssignedModified() (time.Time, error) {
|
|
||||||
return time.Time{}, fmt.Errorf("AssignedModified method not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fakeStore) Assigned() (checkpoint.RemoteConfigSource, error) {
|
|
||||||
return s.assigned, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fakeStore) LastKnownGood() (checkpoint.RemoteConfigSource, error) {
|
|
||||||
return s.lastKnownGood, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fakeStore) SetAssigned(source checkpoint.RemoteConfigSource) error {
|
|
||||||
s.assigned = source
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fakeStore) SetLastKnownGood(source checkpoint.RemoteConfigSource) error {
|
|
||||||
s.lastKnownGood = source
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fakeStore) Reset() (bool, error) {
|
|
||||||
return false, fmt.Errorf("Reset method not supported")
|
|
||||||
}
|
|
@ -1,194 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint"
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/configfiles"
|
|
||||||
utilfiles "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/files"
|
|
||||||
utilfs "k8s.io/kubernetes/pkg/util/filesystem"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
metaDir = "meta"
|
|
||||||
assignedFile = "assigned"
|
|
||||||
lastKnownGoodFile = "last-known-good"
|
|
||||||
|
|
||||||
checkpointsDir = "checkpoints"
|
|
||||||
)
|
|
||||||
|
|
||||||
// fsStore is for tracking checkpoints in the local filesystem, implements Store
|
|
||||||
type fsStore struct {
|
|
||||||
// fs is the filesystem to use for storage operations; can be mocked for testing
|
|
||||||
fs utilfs.Filesystem
|
|
||||||
// dir is the absolute path to the storage directory for fsStore
|
|
||||||
dir string
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Store = (*fsStore)(nil)
|
|
||||||
|
|
||||||
// NewFsStore returns a Store that saves its data in dir
|
|
||||||
func NewFsStore(fs utilfs.Filesystem, dir string) Store {
|
|
||||||
return &fsStore{
|
|
||||||
fs: fs,
|
|
||||||
dir: dir,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fsStore) Initialize() error {
|
|
||||||
klog.InfoS("Kubelet config controller initializing config checkpoints directory", "path", s.dir)
|
|
||||||
// ensure top-level dir for store
|
|
||||||
if err := utilfiles.EnsureDir(s.fs, s.dir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// ensure metadata directory and reference files (tracks assigned and lkg configs)
|
|
||||||
if err := utilfiles.EnsureDir(s.fs, filepath.Join(s.dir, metaDir)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := utilfiles.EnsureFile(s.fs, s.metaPath(assignedFile)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := utilfiles.EnsureFile(s.fs, s.metaPath(lastKnownGoodFile)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// ensure checkpoints directory (saves unpacked payloads in subdirectories named after payload UID)
|
|
||||||
return utilfiles.EnsureDir(s.fs, filepath.Join(s.dir, checkpointsDir))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fsStore) Exists(source checkpoint.RemoteConfigSource) (bool, error) {
|
|
||||||
const errfmt = "failed to determine whether checkpoint exists for source %s, UID: %s, ResourceVersion: %s exists, error: %v"
|
|
||||||
if len(source.UID()) == 0 {
|
|
||||||
return false, fmt.Errorf(errfmt, source.APIPath(), source.UID(), source.ResourceVersion(), "empty UID is ambiguous")
|
|
||||||
}
|
|
||||||
if len(source.ResourceVersion()) == 0 {
|
|
||||||
return false, fmt.Errorf(errfmt, source.APIPath(), source.UID(), source.ResourceVersion(), "empty ResourceVersion is ambiguous")
|
|
||||||
}
|
|
||||||
|
|
||||||
// we check whether the directory was created for the resource
|
|
||||||
ok, err := utilfiles.DirExists(s.fs, s.checkpointPath(source.UID(), source.ResourceVersion()))
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf(errfmt, source.APIPath(), source.UID(), source.ResourceVersion(), err)
|
|
||||||
}
|
|
||||||
return ok, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fsStore) Save(payload checkpoint.Payload) error {
|
|
||||||
// Note: Payload interface guarantees UID() and ResourceVersion() to be non-empty
|
|
||||||
path := s.checkpointPath(payload.UID(), payload.ResourceVersion())
|
|
||||||
// ensure the parent dir (checkpoints/uid) exists, since ReplaceDir requires the parent of the replace
|
|
||||||
// to exist, and we checkpoint as checkpoints/uid/resourceVersion/files-from-configmap
|
|
||||||
if err := utilfiles.EnsureDir(s.fs, filepath.Dir(path)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// save the checkpoint's files in the appropriate checkpoint dir
|
|
||||||
return utilfiles.ReplaceDir(s.fs, path, payload.Files())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fsStore) Load(source checkpoint.RemoteConfigSource) (*kubeletconfig.KubeletConfiguration, error) {
|
|
||||||
sourceFmt := fmt.Sprintf("%s, UID: %s, ResourceVersion: %s", source.APIPath(), source.UID(), source.ResourceVersion())
|
|
||||||
// check if a checkpoint exists for the source
|
|
||||||
if ok, err := s.Exists(source); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if !ok {
|
|
||||||
return nil, fmt.Errorf("no checkpoint for source %s", sourceFmt)
|
|
||||||
}
|
|
||||||
// load the kubelet config file
|
|
||||||
klog.InfoS("Kubelet config controller loading Kubelet configuration checkpoint for source", "apiPath", source.APIPath(), "sourceUID", source.UID(), "resourceVersion", source.ResourceVersion())
|
|
||||||
loader, err := configfiles.NewFsLoader(s.fs, filepath.Join(s.checkpointPath(source.UID(), source.ResourceVersion()), source.KubeletFilename()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
kc, err := loader.Load()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return kc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fsStore) AssignedModified() (time.Time, error) {
|
|
||||||
path := s.metaPath(assignedFile)
|
|
||||||
info, err := s.fs.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
return time.Time{}, fmt.Errorf("failed to stat %q while checking modification time, error: %v", path, err)
|
|
||||||
}
|
|
||||||
return info.ModTime(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fsStore) Assigned() (checkpoint.RemoteConfigSource, error) {
|
|
||||||
return readRemoteConfigSource(s.fs, s.metaPath(assignedFile))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fsStore) LastKnownGood() (checkpoint.RemoteConfigSource, error) {
|
|
||||||
return readRemoteConfigSource(s.fs, s.metaPath(lastKnownGoodFile))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fsStore) SetAssigned(source checkpoint.RemoteConfigSource) error {
|
|
||||||
return writeRemoteConfigSource(s.fs, s.metaPath(assignedFile), source)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fsStore) SetLastKnownGood(source checkpoint.RemoteConfigSource) error {
|
|
||||||
return writeRemoteConfigSource(s.fs, s.metaPath(lastKnownGoodFile), source)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fsStore) Reset() (bool, error) {
|
|
||||||
return reset(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fsStore) checkpointPath(uid, resourceVersion string) string {
|
|
||||||
return filepath.Join(s.dir, checkpointsDir, uid, resourceVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fsStore) metaPath(name string) string {
|
|
||||||
return filepath.Join(s.dir, metaDir, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readRemoteConfigSource(fs utilfs.Filesystem, path string) (checkpoint.RemoteConfigSource, error) {
|
|
||||||
data, err := fs.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if len(data) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return checkpoint.DecodeRemoteConfigSource(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeRemoteConfigSource(fs utilfs.Filesystem, path string, source checkpoint.RemoteConfigSource) error {
|
|
||||||
// if nil, reset the file
|
|
||||||
if source == nil {
|
|
||||||
return utilfiles.ReplaceFile(fs, path, []byte{})
|
|
||||||
}
|
|
||||||
// check that UID and ResourceVersion are non-empty,
|
|
||||||
// error to save reference if the checkpoint can't be fully resolved
|
|
||||||
if source.UID() == "" {
|
|
||||||
return fmt.Errorf("failed to write RemoteConfigSource, empty UID is ambiguous")
|
|
||||||
}
|
|
||||||
if source.ResourceVersion() == "" {
|
|
||||||
return fmt.Errorf("failed to write RemoteConfigSource, empty ResourceVersion is ambiguous")
|
|
||||||
}
|
|
||||||
// encode the source and save it to the file
|
|
||||||
data, err := source.Encode()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return utilfiles.ReplaceFile(fs, path, data)
|
|
||||||
}
|
|
@ -1,727 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"k8s.io/kubelet/config/v1beta1"
|
|
||||||
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/apis/config/scheme"
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint"
|
|
||||||
utilcodec "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/codec"
|
|
||||||
utilfiles "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/files"
|
|
||||||
utiltest "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/test"
|
|
||||||
utilfs "k8s.io/kubernetes/pkg/util/filesystem"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newInitializedFakeFsStore() (*fsStore, error) {
|
|
||||||
// Test with the default filesystem, the fake filesystem has an issue caused by afero: https://github.com/spf13/afero/issues/141
|
|
||||||
// The default filesystem also behaves more like production, so we should probably not mock the filesystem for unit tests.
|
|
||||||
fs := &utilfs.DefaultFs{}
|
|
||||||
|
|
||||||
tmpDir, err := fs.TempDir("", "fsstore-test-")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
store := NewFsStore(fs, tmpDir)
|
|
||||||
if err := store.Initialize(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return store.(*fsStore), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanupFakeFsStore(store *fsStore) {
|
|
||||||
_ = store.fs.RemoveAll(store.dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFsStoreInitialize(t *testing.T) {
|
|
||||||
store, err := newInitializedFakeFsStore()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("fsStore.Initialize() failed with error: %v", err)
|
|
||||||
}
|
|
||||||
defer cleanupFakeFsStore(store)
|
|
||||||
|
|
||||||
// check that store.dir exists
|
|
||||||
if _, err := store.fs.Stat(store.dir); err != nil {
|
|
||||||
t.Fatalf("expect %q to exist, but stat failed with error: %v", store.dir, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that meta dir exists
|
|
||||||
if _, err := store.fs.Stat(store.metaPath("")); err != nil {
|
|
||||||
t.Fatalf("expect %q to exist, but stat failed with error: %v", store.metaPath(""), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that checkpoints dir exists
|
|
||||||
if _, err := store.fs.Stat(filepath.Join(store.dir, checkpointsDir)); err != nil {
|
|
||||||
t.Fatalf("expect %q to exist, but stat failed with error: %v", filepath.Join(store.dir, checkpointsDir), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that assignedFile exists
|
|
||||||
if _, err := store.fs.Stat(store.metaPath(assignedFile)); err != nil {
|
|
||||||
t.Fatalf("expect %q to exist, but stat failed with error: %v", store.metaPath(assignedFile), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that lastKnownGoodFile exists
|
|
||||||
if _, err := store.fs.Stat(store.metaPath(lastKnownGoodFile)); err != nil {
|
|
||||||
t.Fatalf("expect %q to exist, but stat failed with error: %v", store.metaPath(lastKnownGoodFile), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFsStoreExists(t *testing.T) {
|
|
||||||
store, err := newInitializedFakeFsStore()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing store: %v", err)
|
|
||||||
}
|
|
||||||
defer cleanupFakeFsStore(store)
|
|
||||||
|
|
||||||
// checkpoint a payload
|
|
||||||
const (
|
|
||||||
uid = "uid"
|
|
||||||
resourceVersion = "1"
|
|
||||||
)
|
|
||||||
p, err := checkpoint.NewConfigMapPayload(&apiv1.ConfigMap{ObjectMeta: metav1.ObjectMeta{UID: uid, ResourceVersion: resourceVersion}})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not construct Payload, error: %v", err)
|
|
||||||
}
|
|
||||||
if err := store.Save(p); err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
uid types.UID
|
|
||||||
resourceVersion string
|
|
||||||
expect bool
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
{"exists", uid, resourceVersion, true, ""},
|
|
||||||
{"does not exist", "bogus-uid", "bogus-resourceVersion", false, ""},
|
|
||||||
{"ambiguous UID", "", "bogus-resourceVersion", false, "empty UID is ambiguous"},
|
|
||||||
{"ambiguous ResourceVersion", "bogus-uid", "", false, "empty ResourceVersion is ambiguous"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(c.desc, func(t *testing.T) {
|
|
||||||
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{
|
|
||||||
ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
UID: c.uid,
|
|
||||||
ResourceVersion: c.resourceVersion,
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
ok, err := store.Exists(source)
|
|
||||||
utiltest.ExpectError(t, err, c.err)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.expect != ok {
|
|
||||||
t.Errorf("expect %t but got %t", c.expect, ok)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFsStoreSave(t *testing.T) {
|
|
||||||
store, err := newInitializedFakeFsStore()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing store: %v", err)
|
|
||||||
}
|
|
||||||
defer cleanupFakeFsStore(store)
|
|
||||||
|
|
||||||
nameTooLong := func() string {
|
|
||||||
s := ""
|
|
||||||
for i := 0; i < 256; i++ {
|
|
||||||
s += "a"
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}()
|
|
||||||
|
|
||||||
const (
|
|
||||||
uid = "uid"
|
|
||||||
resourceVersion = "1"
|
|
||||||
)
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
uid types.UID
|
|
||||||
resourceVersion string
|
|
||||||
files map[string]string
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
{"valid payload", uid, resourceVersion, map[string]string{"foo": "foocontent", "bar": "barcontent"}, ""},
|
|
||||||
{"empty key name", uid, resourceVersion, map[string]string{"": "foocontent"}, "must not be empty"},
|
|
||||||
{"key name is not a base file name (foo/bar)", uid, resourceVersion, map[string]string{"foo/bar": "foocontent"}, "only base names are allowed"},
|
|
||||||
{"key name is not a base file name (/foo)", uid, resourceVersion, map[string]string{"/bar": "foocontent"}, "only base names are allowed"},
|
|
||||||
{"used .", uid, resourceVersion, map[string]string{".": "foocontent"}, "may not be '.' or '..'"},
|
|
||||||
{"used ..", uid, resourceVersion, map[string]string{"..": "foocontent"}, "may not be '.' or '..'"},
|
|
||||||
{"length violation", uid, resourceVersion, map[string]string{nameTooLong: "foocontent"}, "must be less than 255 characters"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(c.desc, func(t *testing.T) {
|
|
||||||
// construct the payload
|
|
||||||
p, err := checkpoint.NewConfigMapPayload(&apiv1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{UID: c.uid, ResourceVersion: c.resourceVersion},
|
|
||||||
Data: c.files,
|
|
||||||
})
|
|
||||||
// if no error, save the payload, otherwise skip straight to error handler
|
|
||||||
if err == nil {
|
|
||||||
err = store.Save(p)
|
|
||||||
}
|
|
||||||
utiltest.ExpectError(t, err, c.err)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// read the saved checkpoint
|
|
||||||
m, err := mapFromCheckpoint(store, p.UID(), p.ResourceVersion())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error loading checkpoint to map: %v", err)
|
|
||||||
}
|
|
||||||
// compare our expectation to what got saved
|
|
||||||
expect := p.Files()
|
|
||||||
if !reflect.DeepEqual(expect, m) {
|
|
||||||
t.Errorf("expect %v, but got %v", expect, m)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFsStoreLoad(t *testing.T) {
|
|
||||||
store, err := newInitializedFakeFsStore()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing store: %v", err)
|
|
||||||
}
|
|
||||||
defer cleanupFakeFsStore(store)
|
|
||||||
|
|
||||||
// encode a kubelet configuration that has all defaults set
|
|
||||||
expect, err := newKubeletConfiguration()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing KubeletConfiguration: %v", err)
|
|
||||||
}
|
|
||||||
data, err := utilcodec.EncodeKubeletConfig(expect, v1beta1.SchemeGroupVersion)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error encoding KubeletConfiguration: %v", err)
|
|
||||||
}
|
|
||||||
// construct a payload that contains the kubeletconfig
|
|
||||||
const (
|
|
||||||
uid = "uid"
|
|
||||||
resourceVersion = "1"
|
|
||||||
kubeletKey = "kubelet"
|
|
||||||
)
|
|
||||||
p, err := checkpoint.NewConfigMapPayload(&apiv1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{UID: types.UID(uid), ResourceVersion: resourceVersion},
|
|
||||||
Data: map[string]string{
|
|
||||||
kubeletKey: string(data),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing payload: %v", err)
|
|
||||||
}
|
|
||||||
// save the payload
|
|
||||||
err = store.Save(p)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error saving payload: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
uid types.UID
|
|
||||||
resourceVersion string
|
|
||||||
err string
|
|
||||||
skipOnWindows bool
|
|
||||||
}{
|
|
||||||
{"checkpoint exists", uid, resourceVersion, "", true},
|
|
||||||
{"checkpoint does not exist", "bogus-uid", "bogus-resourceVersion", "no checkpoint for source", false},
|
|
||||||
{"ambiguous UID", "", "bogus-resourceVersion", "empty UID is ambiguous", false},
|
|
||||||
{"ambiguous ResourceVersion", "bogus-uid", "", "empty ResourceVersion is ambiguous", false},
|
|
||||||
}
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(c.desc, func(t *testing.T) {
|
|
||||||
// Skip tests that fail on Windows, as discussed during the SIG Testing meeting from January 10, 2023
|
|
||||||
if c.skipOnWindows && runtime.GOOS == "windows" {
|
|
||||||
t.Skip("Skipping test that fails on Windows")
|
|
||||||
}
|
|
||||||
|
|
||||||
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{
|
|
||||||
ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
UID: c.uid,
|
|
||||||
ResourceVersion: c.resourceVersion,
|
|
||||||
KubeletConfigKey: kubeletKey,
|
|
||||||
}})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
loaded, err := store.Load(source)
|
|
||||||
utiltest.ExpectError(t, err, c.err)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(expect, loaded) {
|
|
||||||
t.Errorf("expect %#v, but got %#v", expect, loaded)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFsStoreAssignedModified(t *testing.T) {
|
|
||||||
store, err := newInitializedFakeFsStore()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing store: %v", err)
|
|
||||||
}
|
|
||||||
defer cleanupFakeFsStore(store)
|
|
||||||
|
|
||||||
// create an empty assigned file, this is good enough for testing
|
|
||||||
saveTestSourceFile(t, store, assignedFile, nil)
|
|
||||||
|
|
||||||
// round the current time to the nearest second because some file systems do not support sub-second precision.
|
|
||||||
now := time.Now().Round(time.Second)
|
|
||||||
// set the timestamps to the current time, so we can compare to result of store.AssignedModified
|
|
||||||
err = store.fs.Chtimes(store.metaPath(assignedFile), now, now)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not change timestamps, error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
modTime, err := store.AssignedModified()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to determine modification time of assigned config source, error: %v", err)
|
|
||||||
}
|
|
||||||
if !now.Equal(modTime) {
|
|
||||||
t.Errorf("expect %q but got %q", now.Format(time.RFC3339), modTime.Format(time.RFC3339))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFsStoreAssigned(t *testing.T) {
|
|
||||||
store, err := newInitializedFakeFsStore()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing store: %v", err)
|
|
||||||
}
|
|
||||||
defer cleanupFakeFsStore(store)
|
|
||||||
|
|
||||||
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{
|
|
||||||
ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
UID: "uid",
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
expect checkpoint.RemoteConfigSource
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
{"default source", nil, ""},
|
|
||||||
{"non-default source", source, ""},
|
|
||||||
}
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(c.desc, func(t *testing.T) {
|
|
||||||
// save the last known good source
|
|
||||||
saveTestSourceFile(t, store, assignedFile, c.expect)
|
|
||||||
|
|
||||||
// load last-known-good and compare to expected result
|
|
||||||
source, err := store.Assigned()
|
|
||||||
utiltest.ExpectError(t, err, c.err)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !checkpoint.EqualRemoteConfigSources(c.expect, source) {
|
|
||||||
t.Errorf("case %q, expect %q but got %q", spew.Sdump(c.expect), spew.Sdump(c.expect), spew.Sdump(source))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFsStoreLastKnownGood(t *testing.T) {
|
|
||||||
store, err := newInitializedFakeFsStore()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing store: %v", err)
|
|
||||||
}
|
|
||||||
defer cleanupFakeFsStore(store)
|
|
||||||
|
|
||||||
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{
|
|
||||||
ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
UID: "uid",
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
expect checkpoint.RemoteConfigSource
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
{"default source", nil, ""},
|
|
||||||
{"non-default source", source, ""},
|
|
||||||
}
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(c.desc, func(t *testing.T) {
|
|
||||||
// save the last known good source
|
|
||||||
saveTestSourceFile(t, store, lastKnownGoodFile, c.expect)
|
|
||||||
|
|
||||||
// load last-known-good and compare to expected result
|
|
||||||
source, err := store.LastKnownGood()
|
|
||||||
utiltest.ExpectError(t, err, c.err)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !checkpoint.EqualRemoteConfigSources(c.expect, source) {
|
|
||||||
t.Errorf("case %q, expect %q but got %q", spew.Sdump(c.expect), spew.Sdump(c.expect), spew.Sdump(source))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFsStoreSetAssigned(t *testing.T) {
|
|
||||||
store, err := newInitializedFakeFsStore()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing store: %v", err)
|
|
||||||
}
|
|
||||||
defer cleanupFakeFsStore(store)
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
source *apiv1.NodeConfigSource
|
|
||||||
expect string
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "nil source",
|
|
||||||
expect: "", // empty file
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "non-nil source",
|
|
||||||
source: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
UID: "uid",
|
|
||||||
ResourceVersion: "1",
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}},
|
|
||||||
expect: `apiVersion: kubelet.config.k8s.io/v1beta1
|
|
||||||
kind: SerializedNodeConfigSource
|
|
||||||
source:
|
|
||||||
configMap:
|
|
||||||
kubeletConfigKey: kubelet
|
|
||||||
name: name
|
|
||||||
namespace: namespace
|
|
||||||
resourceVersion: "1"
|
|
||||||
uid: uid
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "missing UID",
|
|
||||||
source: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
ResourceVersion: "1",
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}},
|
|
||||||
err: "failed to write RemoteConfigSource, empty UID is ambiguous",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "missing ResourceVersion",
|
|
||||||
source: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
UID: "uid",
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}},
|
|
||||||
err: "failed to write RemoteConfigSource, empty ResourceVersion is ambiguous",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(c.desc, func(t *testing.T) {
|
|
||||||
var source checkpoint.RemoteConfigSource
|
|
||||||
if c.source != nil {
|
|
||||||
s, _, err := checkpoint.NewRemoteConfigSource(c.source)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
source = s
|
|
||||||
}
|
|
||||||
// save the assigned source
|
|
||||||
err = store.SetAssigned(source)
|
|
||||||
utiltest.ExpectError(t, err, c.err)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// check that the source saved as we would expect
|
|
||||||
data := readTestSourceFile(t, store, assignedFile)
|
|
||||||
if c.expect != string(data) {
|
|
||||||
t.Errorf("expect assigned source file to contain %q, but got %q", c.expect, string(data))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFsStoreSetLastKnownGood(t *testing.T) {
|
|
||||||
store, err := newInitializedFakeFsStore()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing store: %v", err)
|
|
||||||
}
|
|
||||||
defer cleanupFakeFsStore(store)
|
|
||||||
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
source *apiv1.NodeConfigSource
|
|
||||||
expect string
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "nil source",
|
|
||||||
expect: "", // empty file
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "non-nil source",
|
|
||||||
source: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
UID: "uid",
|
|
||||||
ResourceVersion: "1",
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}},
|
|
||||||
expect: `apiVersion: kubelet.config.k8s.io/v1beta1
|
|
||||||
kind: SerializedNodeConfigSource
|
|
||||||
source:
|
|
||||||
configMap:
|
|
||||||
kubeletConfigKey: kubelet
|
|
||||||
name: name
|
|
||||||
namespace: namespace
|
|
||||||
resourceVersion: "1"
|
|
||||||
uid: uid
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "missing UID",
|
|
||||||
source: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
ResourceVersion: "1",
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}},
|
|
||||||
err: "failed to write RemoteConfigSource, empty UID is ambiguous",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "missing ResourceVersion",
|
|
||||||
source: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
UID: "uid",
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}},
|
|
||||||
err: "failed to write RemoteConfigSource, empty ResourceVersion is ambiguous",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(c.desc, func(t *testing.T) {
|
|
||||||
var source checkpoint.RemoteConfigSource
|
|
||||||
if c.source != nil {
|
|
||||||
s, _, err := checkpoint.NewRemoteConfigSource(c.source)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
source = s
|
|
||||||
}
|
|
||||||
// save the assigned source
|
|
||||||
err = store.SetLastKnownGood(source)
|
|
||||||
utiltest.ExpectError(t, err, c.err)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// check that the source saved as we would expect
|
|
||||||
data := readTestSourceFile(t, store, lastKnownGoodFile)
|
|
||||||
if c.expect != string(data) {
|
|
||||||
t.Errorf("expect assigned source file to contain %q, but got %q", c.expect, string(data))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFsStoreReset(t *testing.T) {
|
|
||||||
store, err := newInitializedFakeFsStore()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error constructing store: %v", err)
|
|
||||||
}
|
|
||||||
defer cleanupFakeFsStore(store)
|
|
||||||
|
|
||||||
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
UID: "uid",
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
otherSource, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "other-name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
UID: "other-uid",
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
assigned checkpoint.RemoteConfigSource
|
|
||||||
lastKnownGood checkpoint.RemoteConfigSource
|
|
||||||
updated bool
|
|
||||||
}{
|
|
||||||
{"nil -> nil", nil, nil, false},
|
|
||||||
{"source -> nil", source, nil, true},
|
|
||||||
{"nil -> source", nil, source, false},
|
|
||||||
{"source -> source", source, source, true},
|
|
||||||
{"source -> otherSource", source, otherSource, true},
|
|
||||||
{"otherSource -> source", otherSource, source, true},
|
|
||||||
}
|
|
||||||
for _, c := range cases {
|
|
||||||
t.Run(c.desc, func(t *testing.T) {
|
|
||||||
// manually save the sources to their respective files
|
|
||||||
saveTestSourceFile(t, store, assignedFile, c.assigned)
|
|
||||||
saveTestSourceFile(t, store, lastKnownGoodFile, c.lastKnownGood)
|
|
||||||
|
|
||||||
// reset
|
|
||||||
updated, err := store.Reset()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the files were emptied
|
|
||||||
if size := testSourceFileSize(t, store, assignedFile); size > 0 {
|
|
||||||
t.Errorf("case %q, expect source file %q to be empty but got %d bytes", c.desc, assignedFile, size)
|
|
||||||
}
|
|
||||||
if size := testSourceFileSize(t, store, lastKnownGoodFile); size > 0 {
|
|
||||||
t.Errorf("case %q, expect source file %q to be empty but got %d bytes", c.desc, lastKnownGoodFile, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure Assigned() and LastKnownGood() both return nil
|
|
||||||
assigned, err := store.Assigned()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
lastKnownGood, err := store.LastKnownGood()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if assigned != nil || lastKnownGood != nil {
|
|
||||||
t.Errorf("case %q, expect nil for assigned and last-known-good checkpoints, but still have %q and %q, respectively",
|
|
||||||
c.desc, assigned, lastKnownGood)
|
|
||||||
}
|
|
||||||
if c.updated != updated {
|
|
||||||
t.Errorf("case %q, expect reset to return %t, but got %t", c.desc, c.updated, updated)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapFromCheckpoint(store *fsStore, uid, resourceVersion string) (map[string]string, error) {
|
|
||||||
files, err := store.fs.ReadDir(store.checkpointPath(uid, resourceVersion))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m := map[string]string{}
|
|
||||||
for _, f := range files {
|
|
||||||
// expect no subdirs, only regular files
|
|
||||||
if !f.Type().IsRegular() {
|
|
||||||
return nil, fmt.Errorf("expect only regular files in checkpoint dir %q", uid)
|
|
||||||
}
|
|
||||||
// read the file contents and build the map
|
|
||||||
data, err := store.fs.ReadFile(filepath.Join(store.checkpointPath(uid, resourceVersion), f.Name()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m[f.Name()] = string(data)
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readTestSourceFile(t *testing.T, store *fsStore, relPath string) []byte {
|
|
||||||
data, err := store.fs.ReadFile(store.metaPath(relPath))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to read test source file, error: %v", err)
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveTestSourceFile(t *testing.T, store *fsStore, relPath string, source checkpoint.RemoteConfigSource) {
|
|
||||||
if source != nil {
|
|
||||||
data, err := source.Encode()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to save test source file, error: %v", err)
|
|
||||||
}
|
|
||||||
err = utilfiles.ReplaceFile(store.fs, store.metaPath(relPath), data)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to save test source file, error: %v", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err := utilfiles.ReplaceFile(store.fs, store.metaPath(relPath), []byte{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to save test source file, error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSourceFileSize(t *testing.T, store *fsStore, relPath string) int64 {
|
|
||||||
info, err := store.fs.Stat(store.metaPath(relPath))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
return info.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
// newKubeletConfiguration will create a new KubeletConfiguration with default values set
|
|
||||||
func newKubeletConfiguration() (*kubeletconfig.KubeletConfiguration, error) {
|
|
||||||
s, _, err := scheme.NewSchemeAndCodecs()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
versioned := &v1beta1.KubeletConfiguration{}
|
|
||||||
s.Default(versioned)
|
|
||||||
config := &kubeletconfig.KubeletConfiguration{}
|
|
||||||
if err := s.Convert(versioned, config, nil); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return config, nil
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Store saves checkpoints and information about which is the assigned and last-known-good checkpoint to a storage layer
|
|
||||||
type Store interface {
|
|
||||||
// Initialize sets up the storage layer
|
|
||||||
Initialize() error
|
|
||||||
|
|
||||||
// Exists returns true if the object referenced by `source` has been checkpointed.
|
|
||||||
// The source must be unambiguous - e.g. if referencing an API object it must specify both uid and resourceVersion.
|
|
||||||
Exists(source checkpoint.RemoteConfigSource) (bool, error)
|
|
||||||
// Save Kubelet config payloads to the storage layer. It must be possible to unmarshal the payload to a KubeletConfiguration.
|
|
||||||
// The following payload types are supported:
|
|
||||||
// - k8s.io/api/core/v1.ConfigMap
|
|
||||||
Save(c checkpoint.Payload) error
|
|
||||||
// Load loads the KubeletConfiguration from the checkpoint referenced by `source`.
|
|
||||||
Load(source checkpoint.RemoteConfigSource) (*kubeletconfig.KubeletConfiguration, error)
|
|
||||||
|
|
||||||
// AssignedModified returns the last time that the assigned checkpoint was set
|
|
||||||
AssignedModified() (time.Time, error)
|
|
||||||
// Assigned returns the source that points to the checkpoint currently assigned to the Kubelet, or nil if no assigned checkpoint is set
|
|
||||||
Assigned() (checkpoint.RemoteConfigSource, error)
|
|
||||||
// LastKnownGood returns the source that points to the last-known-good checkpoint, or nil if no last-known-good checkpoint is set
|
|
||||||
LastKnownGood() (checkpoint.RemoteConfigSource, error)
|
|
||||||
|
|
||||||
// SetAssigned saves the source that points to the assigned checkpoint, set to nil to unset
|
|
||||||
SetAssigned(source checkpoint.RemoteConfigSource) error
|
|
||||||
// SetLastKnownGood saves the source that points to the last-known-good checkpoint, set to nil to unset
|
|
||||||
SetLastKnownGood(source checkpoint.RemoteConfigSource) error
|
|
||||||
// Reset unsets the assigned and last-known-good checkpoints and returns whether the assigned checkpoint was unset as a result of the reset
|
|
||||||
Reset() (bool, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset is a helper for implementing Reset, which can be implemented in terms of Store methods
|
|
||||||
func reset(s Store) (bool, error) {
|
|
||||||
assigned, err := s.Assigned()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if err := s.SetLastKnownGood(nil); err != nil {
|
|
||||||
return false, fmt.Errorf("failed to reset last-known-good UID in checkpoint store, error: %v", err)
|
|
||||||
}
|
|
||||||
if err := s.SetAssigned(nil); err != nil {
|
|
||||||
return false, fmt.Errorf("failed to reset assigned UID in checkpoint store, error: %v", err)
|
|
||||||
}
|
|
||||||
return assigned != nil, nil
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestReset(t *testing.T) {
|
|
||||||
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
UID: "uid",
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
otherSource, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
|
|
||||||
Name: "other-name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
UID: "other-uid",
|
|
||||||
KubeletConfigKey: "kubelet",
|
|
||||||
}})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
cases := []struct {
|
|
||||||
s *fakeStore
|
|
||||||
updated bool
|
|
||||||
}{
|
|
||||||
{&fakeStore{assigned: nil, lastKnownGood: nil}, false},
|
|
||||||
{&fakeStore{assigned: source, lastKnownGood: nil}, true},
|
|
||||||
{&fakeStore{assigned: nil, lastKnownGood: source}, false},
|
|
||||||
{&fakeStore{assigned: source, lastKnownGood: source}, true},
|
|
||||||
{&fakeStore{assigned: source, lastKnownGood: otherSource}, true},
|
|
||||||
{&fakeStore{assigned: otherSource, lastKnownGood: source}, true},
|
|
||||||
}
|
|
||||||
for _, c := range cases {
|
|
||||||
updated, err := reset(c.s)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if c.s.assigned != nil || c.s.lastKnownGood != nil {
|
|
||||||
t.Errorf("case %q, expect nil for assigned and last-known-good checkpoints, but still have %q and %q, respectively",
|
|
||||||
spew.Sdump(c.s), c.s.assigned, c.s.lastKnownGood)
|
|
||||||
}
|
|
||||||
if c.updated != updated {
|
|
||||||
t.Errorf("case %q, expect reset to return %t, but got %t", spew.Sdump(c.s), c.updated, updated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2017 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package status
|
|
||||||
|
|
||||||
import (
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// LoadError indicates that the Kubelet failed to load the config checkpoint
|
|
||||||
LoadError = "failed to load config, see Kubelet log for details"
|
|
||||||
// ValidateError indicates that the Kubelet failed to validate the config checkpoint
|
|
||||||
ValidateError = "failed to validate config, see Kubelet log for details"
|
|
||||||
// AllNilSubfieldsError is used when no subfields are set
|
|
||||||
// This could happen in the case that an old client tries to read an object from a newer API server with a set subfield it does not know about
|
|
||||||
AllNilSubfieldsError = "invalid NodeConfigSource, exactly one subfield must be non-nil, but all were nil"
|
|
||||||
// DownloadError is used when the download fails, e.g. due to network issues
|
|
||||||
DownloadError = "failed to download config, see Kubelet log for details"
|
|
||||||
// InternalError indicates that some internal error happened while trying to sync config, e.g. filesystem issues
|
|
||||||
InternalError = "internal failure, see Kubelet log for details"
|
|
||||||
|
|
||||||
// SyncErrorFmt is used when the system couldn't sync the config, due to a malformed Node.Spec.ConfigSource, a download failure, etc.
|
|
||||||
SyncErrorFmt = "failed to sync: %s"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NodeConfigStatus represents Node.Status.Config
|
|
||||||
type NodeConfigStatus interface {
|
|
||||||
// SetActive sets the active source in the status
|
|
||||||
SetActive(source *apiv1.NodeConfigSource)
|
|
||||||
// SetAssigned sets the assigned source in the status
|
|
||||||
SetAssigned(source *apiv1.NodeConfigSource)
|
|
||||||
// SetLastKnownGood sets the last-known-good source in the status
|
|
||||||
SetLastKnownGood(source *apiv1.NodeConfigSource)
|
|
||||||
// SetError sets the error associated with the status
|
|
||||||
SetError(err string)
|
|
||||||
// SetErrorOverride sets an error that overrides the base error set by SetError.
|
|
||||||
// If the override is set to the empty string, the base error is reported in
|
|
||||||
// the status, otherwise the override is reported.
|
|
||||||
SetErrorOverride(err string)
|
|
||||||
// Sync patches the current status into the Node identified by `nodeName` if an update is pending
|
|
||||||
Sync(client clientset.Interface, nodeName string)
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user