Merge pull request #59847 from mtaufen/dkcfg-explicit-keys

Automatic merge from submit-queue (batch tested with PRs 63624, 59847). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

explicit kubelet config key in Node.Spec.ConfigSource.ConfigMap

This makes the Kubelet config key in the ConfigMap an explicit part of
the API, so we can stop using magic key names.
    
As part of this change, we are retiring ConfigMapRef for ConfigMap.


```release-note
You must now specify Node.Spec.ConfigSource.ConfigMap.KubeletConfigKey when using dynamic Kubelet config to tell the Kubelet which key of the ConfigMap identifies its config file.
```
pull/8/head
Kubernetes Submit Queue 2018-05-09 17:55:13 -07:00 committed by GitHub
commit b2fe2a0a6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 2123 additions and 1267 deletions

View File

@ -75671,6 +75671,36 @@
}
]
},
"io.k8s.api.core.v1.ConfigMapNodeConfigSource": {
"description": "ConfigMapNodeConfigSource contains the information to reference a ConfigMap as a config source for the Node.",
"required": [
"namespace",
"name",
"kubeletConfigKey"
],
"properties": {
"kubeletConfigKey": {
"description": "KubeletConfigKey declares which key of the referenced ConfigMap corresponds to the KubeletConfiguration structure This field is required in all cases.",
"type": "string"
},
"name": {
"description": "Name is the metadata.name of the referenced ConfigMap. This field is required in all cases.",
"type": "string"
},
"namespace": {
"description": "Namespace is the metadata.namespace of the referenced ConfigMap. This field is required in all cases.",
"type": "string"
},
"resourceVersion": {
"description": "ResourceVersion is the metadata.ResourceVersion of the referenced ConfigMap. This field is forbidden in Node.Spec.",
"type": "string"
},
"uid": {
"description": "UID is the metadata.UID of the referenced ConfigMap. This field is currently reqired in Node.Spec.",
"type": "string"
}
}
},
"io.k8s.api.core.v1.ConfigMapProjection": {
"description": "Adapts a ConfigMap into a projected volume.\n\nThe contents of the target ConfigMap's Data field will be presented in a projected volume as files using the keys in the Data field as the file names, unless the items element is populated with specific mappings of keys to paths. Note that this is identical to a configmap volume source without the default mode.",
"properties": {
@ -77242,25 +77272,11 @@
"io.k8s.api.core.v1.NodeConfigSource": {
"description": "NodeConfigSource specifies a source of node configuration. Exactly one subfield (excluding metadata) must be non-nil.",
"properties": {
"apiVersion": {
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources",
"type": "string"
},
"configMapRef": {
"$ref": "#/definitions/io.k8s.api.core.v1.ObjectReference"
},
"kind": {
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds",
"type": "string"
"configMap": {
"description": "ConfigMap is a reference to a Node's ConfigMap",
"$ref": "#/definitions/io.k8s.api.core.v1.ConfigMapNodeConfigSource"
}
},
"x-kubernetes-group-version-kind": [
{
"group": "",
"kind": "NodeConfigSource",
"version": "v1"
}
]
}
},
"io.k8s.api.core.v1.NodeDaemonEndpoints": {
"description": "NodeDaemonEndpoints lists ports opened by daemons running on the Node.",

View File

@ -18718,16 +18718,40 @@
"id": "v1.NodeConfigSource",
"description": "NodeConfigSource specifies a source of node configuration. Exactly one subfield (excluding metadata) must be non-nil.",
"properties": {
"kind": {
"configMap": {
"$ref": "v1.ConfigMapNodeConfigSource",
"description": "ConfigMap is a reference to a Node's ConfigMap"
}
}
},
"v1.ConfigMapNodeConfigSource": {
"id": "v1.ConfigMapNodeConfigSource",
"description": "ConfigMapNodeConfigSource contains the information to reference a ConfigMap as a config source for the Node.",
"required": [
"namespace",
"name",
"kubeletConfigKey"
],
"properties": {
"namespace": {
"type": "string",
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds"
"description": "Namespace is the metadata.namespace of the referenced ConfigMap. This field is required in all cases."
},
"apiVersion": {
"name": {
"type": "string",
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources"
"description": "Name is the metadata.name of the referenced ConfigMap. This field is required in all cases."
},
"configMapRef": {
"$ref": "v1.ObjectReference"
"uid": {
"type": "string",
"description": "UID is the metadata.UID of the referenced ConfigMap. This field is currently reqired in Node.Spec."
},
"resourceVersion": {
"type": "string",
"description": "ResourceVersion is the metadata.ResourceVersion of the referenced ConfigMap. This field is forbidden in Node.Spec."
},
"kubeletConfigKey": {
"type": "string",
"description": "KubeletConfigKey declares which key of the referenced ConfigMap corresponds to the KubeletConfiguration structure This field is required in all cases."
}
}
},

View File

@ -116,10 +116,11 @@ func updateNodeWithConfigMap(client clientset.Interface, nodeName string) error
}
node.Spec.ConfigSource = &v1.NodeConfigSource{
ConfigMapRef: &v1.ObjectReference{
Name: kubeadmconstants.KubeletBaseConfigurationConfigMap,
Namespace: metav1.NamespaceSystem,
UID: kubeletCfg.UID,
ConfigMap: &v1.ConfigMapNodeConfigSource{
Name: kubeadmconstants.KubeletBaseConfigurationConfigMap,
Namespace: metav1.NamespaceSystem,
UID: kubeletCfg.UID,
KubeletConfigKey: kubeadmconstants.KubeletBaseConfigurationConfigMapKey,
},
}

View File

@ -50,7 +50,7 @@ func TestCreateBaseKubeletConfiguration(t *testing.T) {
},
Spec: v1.NodeSpec{
ConfigSource: &v1.NodeConfigSource{
ConfigMapRef: &v1.ObjectReference{
ConfigMap: &v1.ConfigMapNodeConfigSource{
UID: "",
},
},
@ -94,7 +94,7 @@ func TestUpdateNodeWithConfigMap(t *testing.T) {
},
Spec: v1.NodeSpec{
ConfigSource: &v1.NodeConfigSource{
ConfigMapRef: &v1.ObjectReference{
ConfigMap: &v1.ConfigMapNodeConfigSource{
UID: "",
},
},

View File

@ -403,9 +403,6 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<p><a href="#_v1_node">v1.Node</a></p>
</li>
<li>
<p><a href="#_v1_nodeconfigsource">v1.NodeConfigSource</a></p>
</li>
<li>
<p><a href="#_v1_nodelist">v1.NodeList</a></p>
</li>
<li>
@ -3661,6 +3658,68 @@ Examples:<br>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_configmapnodeconfigsource">v1.ConfigMapNodeConfigSource</h3>
<div class="paragraph">
<p>ConfigMapNodeConfigSource contains the information to reference a ConfigMap as a config source for the Node.</p>
</div>
<table class="tableblock frame-all grid-all" style="width:100%; ">
<colgroup>
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
<col style="width:20%;">
</colgroup>
<thead>
<tr>
<th class="tableblock halign-left valign-top">Name</th>
<th class="tableblock halign-left valign-top">Description</th>
<th class="tableblock halign-left valign-top">Required</th>
<th class="tableblock halign-left valign-top">Schema</th>
<th class="tableblock halign-left valign-top">Default</th>
</tr>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">namespace</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Namespace is the metadata.namespace of the referenced ConfigMap. This field is required in all cases.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">name</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Name is the metadata.name of the referenced ConfigMap. This field is required in all cases.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">uid</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">UID is the metadata.UID of the referenced ConfigMap. This field is currently reqired in Node.Spec.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">resourceVersion</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">ResourceVersion is the metadata.ResourceVersion of the referenced ConfigMap. This field is forbidden in Node.Spec.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">kubeletConfigKey</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">KubeletConfigKey declares which key of the referenced ConfigMap corresponds to the KubeletConfiguration structure This field is required in all cases.</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">true</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
</div>
<div class="sect2">
<h3 id="_v1_endpointport">v1.EndpointPort</h3>
@ -5830,24 +5889,10 @@ Examples:<br>
</thead>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">kind</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: <a href="https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds">https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">configMap</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">ConfigMap is a reference to a Node&#8217;s ConfigMap</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">apiVersion</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: <a href="https://git.k8s.io/community/contributors/devel/api-conventions.md#resources">https://git.k8s.io/community/contributors/devel/api-conventions.md#resources</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">configMapRef</p></td>
<td class="tableblock halign-left valign-top"></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_objectreference">v1.ObjectReference</a></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_configmapnodeconfigsource">v1.ConfigMapNodeConfigSource</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>

View File

@ -60,7 +60,6 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&ServiceProxyOptions{},
&NodeList{},
&Node{},
&NodeConfigSource{},
&NodeProxyOptions{},
&Endpoints{},
&EndpointsList{},

View File

@ -3257,12 +3257,48 @@ type NodeSpec struct {
DoNotUse_ExternalID string
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// NodeConfigSource specifies a source of node configuration. Exactly one subfield must be non-nil.
type NodeConfigSource struct {
metav1.TypeMeta
ConfigMapRef *ObjectReference
ConfigMap *ConfigMapNodeConfigSource
}
type ConfigMapNodeConfigSource struct {
// Namespace is the metadata.namespace of the referenced ConfigMap.
// This field is required in all cases.
Namespace string
// Name is the metadata.name of the referenced ConfigMap.
// This field is required in all cases.
Name string
// UID is the metadata.UID of the referenced ConfigMap.
// This field is currently reqired in Node.Spec.
// TODO(#61643): This field will be forbidden in Node.Spec when #61643 is resolved.
// #61643 changes the behavior of dynamic Kubelet config to respect
// ConfigMap updates, and thus removes the ability to pin the Spec to a given UID.
// TODO(#56896): This field will be required in Node.Status when #56896 is resolved.
// #63314 (the PR that resolves #56896) adds a structured status to the Node
// object for reporting information about the config. This status requires UID
// and ResourceVersion, so that it represents a fully-explicit description of
// the configuration in use, while (see previous TODO) the Spec will be
// restricted to namespace/name in #61643.
// +optional
UID types.UID
// ResourceVersion is the metadata.ResourceVersion of the referenced ConfigMap.
// This field is forbidden in Node.Spec.
// TODO(#56896): This field will be required in Node.Status when #56896 is resolved.
// #63314 (the PR that resolves #56896) adds a structured status to the Node
// object for reporting information about the config. This status requires UID
// and ResourceVersion, so that it represents a fully-explicit description of
// the configuration in use, while (see previous TODO) the Spec will be
// restricted to namespace/name in #61643.
// +optional
ResourceVersion string
// KubeletConfigKey declares which key of the referenced ConfigMap corresponds to the KubeletConfiguration structure
// This field is required in all cases.
KubeletConfigKey string
}
// DaemonEndpoint contains information about a single Daemon endpoint.

View File

@ -82,6 +82,8 @@ func RegisterConversions(scheme *runtime.Scheme) error {
Convert_core_ConfigMapKeySelector_To_v1_ConfigMapKeySelector,
Convert_v1_ConfigMapList_To_core_ConfigMapList,
Convert_core_ConfigMapList_To_v1_ConfigMapList,
Convert_v1_ConfigMapNodeConfigSource_To_core_ConfigMapNodeConfigSource,
Convert_core_ConfigMapNodeConfigSource_To_v1_ConfigMapNodeConfigSource,
Convert_v1_ConfigMapProjection_To_core_ConfigMapProjection,
Convert_core_ConfigMapProjection_To_v1_ConfigMapProjection,
Convert_v1_ConfigMapVolumeSource_To_core_ConfigMapVolumeSource,
@ -941,6 +943,34 @@ func Convert_core_ConfigMapList_To_v1_ConfigMapList(in *core.ConfigMapList, out
return autoConvert_core_ConfigMapList_To_v1_ConfigMapList(in, out, s)
}
func autoConvert_v1_ConfigMapNodeConfigSource_To_core_ConfigMapNodeConfigSource(in *v1.ConfigMapNodeConfigSource, out *core.ConfigMapNodeConfigSource, s conversion.Scope) error {
out.Namespace = in.Namespace
out.Name = in.Name
out.UID = types.UID(in.UID)
out.ResourceVersion = in.ResourceVersion
out.KubeletConfigKey = in.KubeletConfigKey
return nil
}
// Convert_v1_ConfigMapNodeConfigSource_To_core_ConfigMapNodeConfigSource is an autogenerated conversion function.
func Convert_v1_ConfigMapNodeConfigSource_To_core_ConfigMapNodeConfigSource(in *v1.ConfigMapNodeConfigSource, out *core.ConfigMapNodeConfigSource, s conversion.Scope) error {
return autoConvert_v1_ConfigMapNodeConfigSource_To_core_ConfigMapNodeConfigSource(in, out, s)
}
func autoConvert_core_ConfigMapNodeConfigSource_To_v1_ConfigMapNodeConfigSource(in *core.ConfigMapNodeConfigSource, out *v1.ConfigMapNodeConfigSource, s conversion.Scope) error {
out.Namespace = in.Namespace
out.Name = in.Name
out.UID = types.UID(in.UID)
out.ResourceVersion = in.ResourceVersion
out.KubeletConfigKey = in.KubeletConfigKey
return nil
}
// Convert_core_ConfigMapNodeConfigSource_To_v1_ConfigMapNodeConfigSource is an autogenerated conversion function.
func Convert_core_ConfigMapNodeConfigSource_To_v1_ConfigMapNodeConfigSource(in *core.ConfigMapNodeConfigSource, out *v1.ConfigMapNodeConfigSource, s conversion.Scope) error {
return autoConvert_core_ConfigMapNodeConfigSource_To_v1_ConfigMapNodeConfigSource(in, out, s)
}
func autoConvert_v1_ConfigMapProjection_To_core_ConfigMapProjection(in *v1.ConfigMapProjection, out *core.ConfigMapProjection, s conversion.Scope) error {
if err := Convert_v1_LocalObjectReference_To_core_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil {
return err
@ -2586,7 +2616,7 @@ func Convert_core_NodeCondition_To_v1_NodeCondition(in *core.NodeCondition, out
}
func autoConvert_v1_NodeConfigSource_To_core_NodeConfigSource(in *v1.NodeConfigSource, out *core.NodeConfigSource, s conversion.Scope) error {
out.ConfigMapRef = (*core.ObjectReference)(unsafe.Pointer(in.ConfigMapRef))
out.ConfigMap = (*core.ConfigMapNodeConfigSource)(unsafe.Pointer(in.ConfigMap))
return nil
}
@ -2596,7 +2626,7 @@ func Convert_v1_NodeConfigSource_To_core_NodeConfigSource(in *v1.NodeConfigSourc
}
func autoConvert_core_NodeConfigSource_To_v1_NodeConfigSource(in *core.NodeConfigSource, out *v1.NodeConfigSource, s conversion.Scope) error {
out.ConfigMapRef = (*v1.ObjectReference)(unsafe.Pointer(in.ConfigMapRef))
out.ConfigMap = (*v1.ConfigMapNodeConfigSource)(unsafe.Pointer(in.ConfigMap))
return nil
}

View File

@ -4116,7 +4116,7 @@ func ValidateNodeUpdate(node, oldNode *core.Node) field.ErrorList {
// Allow updates to Node.Spec.ConfigSource if DynamicKubeletConfig feature gate is enabled
if utilfeature.DefaultFeatureGate.Enabled(features.DynamicKubeletConfig) {
if node.Spec.ConfigSource != nil {
allErrs = append(allErrs, validateNodeConfigSource(node.Spec.ConfigSource, field.NewPath("spec", "configSource"))...)
allErrs = append(allErrs, validateNodeConfigSourceSpec(node.Spec.ConfigSource, field.NewPath("spec", "configSource"))...)
}
oldNode.Spec.ConfigSource = node.Spec.ConfigSource
}
@ -4131,15 +4131,13 @@ func ValidateNodeUpdate(node, oldNode *core.Node) field.ErrorList {
return allErrs
}
func validateNodeConfigSource(source *core.NodeConfigSource, fldPath *field.Path) field.ErrorList {
// validation specific to Node.Spec.ConfigSource
func validateNodeConfigSourceSpec(source *core.NodeConfigSource, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
count := int(0)
if ref := source.ConfigMapRef; ref != nil {
if source.ConfigMap != nil {
count++
// name, namespace, and UID must all be non-empty for ConfigMapRef
if ref.Name == "" || ref.Namespace == "" || string(ref.UID) == "" {
allErrs = append(allErrs, field.Invalid(fldPath.Child("configMapRef"), ref, "name, namespace, and UID must all be non-empty"))
}
allErrs = append(allErrs, validateConfigMapNodeConfigSourceSpec(source.ConfigMap, fldPath.Child("configMap"))...)
}
// add more subfields here in the future as they are added to NodeConfigSource
@ -4150,6 +4148,50 @@ func validateNodeConfigSource(source *core.NodeConfigSource, fldPath *field.Path
return allErrs
}
// validation specific to Node.Spec.ConfigSource.ConfigMap
func validateConfigMapNodeConfigSourceSpec(source *core.ConfigMapNodeConfigSource, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
// TODO(#61643): Prevent ref.UID from being set here when we switch from requiring UID to respecting all ConfigMap updates
if string(source.UID) == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("uid"), "uid must be set in spec"))
}
// resourceVersion must not be set in spec
if source.ResourceVersion != "" {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("resourceVersion"), "resourceVersion must not be set in spec"))
}
return append(allErrs, validateConfigMapNodeConfigSource(source, fldPath)...)
}
// common validation
func validateConfigMapNodeConfigSource(source *core.ConfigMapNodeConfigSource, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
// validate target configmap namespace
if source.Namespace == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("namespace"), "namespace must be set in spec"))
} else {
for _, msg := range ValidateNameFunc(ValidateNamespaceName)(source.Namespace, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), source.Namespace, msg))
}
}
// validate target configmap name
if source.Name == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("name"), "name must be set in spec"))
} else {
for _, msg := range ValidateNameFunc(ValidateConfigMapName)(source.Name, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), source.Name, msg))
}
}
// validate kubeletConfigKey against rules for configMap key names
if source.KubeletConfigKey == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("kubeletConfigKey"), "kubeletConfigKey must be set in spec"))
} else {
for _, msg := range validation.IsConfigMapKey(source.KubeletConfigKey) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("kubeletConfigKey"), source.KubeletConfigKey, msg))
}
}
return allErrs
}
// Validate compute resource typename.
// Refer to docs/design/resources.md for more details.
func validateResourceName(value string, fldPath *field.Path) field.ErrorList {

View File

@ -631,6 +631,22 @@ func (in *ConfigMapList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConfigMapNodeConfigSource) DeepCopyInto(out *ConfigMapNodeConfigSource) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapNodeConfigSource.
func (in *ConfigMapNodeConfigSource) DeepCopy() *ConfigMapNodeConfigSource {
if in == nil {
return nil
}
out := new(ConfigMapNodeConfigSource)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConfigMapProjection) DeepCopyInto(out *ConfigMapProjection) {
*out = *in
@ -2364,13 +2380,12 @@ func (in *NodeCondition) DeepCopy() *NodeCondition {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NodeConfigSource) DeepCopyInto(out *NodeConfigSource) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.ConfigMapRef != nil {
in, out := &in.ConfigMapRef, &out.ConfigMapRef
if in.ConfigMap != nil {
in, out := &in.ConfigMap, &out.ConfigMap
if *in == nil {
*out = nil
} else {
*out = new(ObjectReference)
*out = new(ConfigMapNodeConfigSource)
**out = **in
}
}
@ -2387,14 +2402,6 @@ func (in *NodeConfigSource) DeepCopy() *NodeConfigSource {
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *NodeConfigSource) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NodeDaemonEndpoints) DeepCopyInto(out *NodeDaemonEndpoints) {
*out = *in

View File

@ -17,6 +17,7 @@ go_library(
],
importpath = "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig",
deps = [
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",

View File

@ -46,6 +46,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
// TODO this will get cleaned up with the scheme types are fixed
scheme.AddKnownTypes(SchemeGroupVersion,
&KubeletConfiguration{},
&SerializedNodeConfigSource{},
)
return nil
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package kubeletconfig
import (
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -333,3 +334,15 @@ type KubeletAnonymousAuthentication struct {
// Anonymous requests have a username of system:anonymous, and a group name of system:unauthenticated.
Enabled bool
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// SerializedNodeConfigSource allows us to serialize NodeConfigSource
// This type is used internally by the Kubelet for tracking checkpointed dynamic configs.
// It exists in the kubeletconfig API group because it is classified as a versioned input to the Kubelet.
type SerializedNodeConfigSource struct {
metav1.TypeMeta
// Source is the source that we are serializing
// +optional
Source v1.NodeConfigSource
}

View File

@ -23,6 +23,7 @@ go_library(
"//pkg/kubelet/types:go_default_library",
"//pkg/master/ports:go_default_library",
"//pkg/util/pointer:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",

View File

@ -45,6 +45,7 @@ func init() {
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&KubeletConfiguration{},
&SerializedNodeConfigSource{},
)
return nil
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package v1beta1
import (
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -512,3 +513,15 @@ type KubeletAnonymousAuthentication struct {
// +optional
Enabled *bool `json:"enabled,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// SerializedNodeConfigSource allows us to serialize v1.NodeConfigSource.
// This type is used internally by the Kubelet for tracking checkpointed dynamic configs.
// It exists in the kubeletconfig API group because it is classified as a versioned input to the Kubelet.
type SerializedNodeConfigSource struct {
metav1.TypeMeta `json:",inline"`
// Source is the source that we are serializing
// +optional
Source v1.NodeConfigSource `json:"source,omitempty" protobuf:"bytes,1,opt,name=source"`
}

View File

@ -51,6 +51,8 @@ func RegisterConversions(scheme *runtime.Scheme) error {
Convert_kubeletconfig_KubeletWebhookAuthorization_To_v1beta1_KubeletWebhookAuthorization,
Convert_v1beta1_KubeletX509Authentication_To_kubeletconfig_KubeletX509Authentication,
Convert_kubeletconfig_KubeletX509Authentication_To_v1beta1_KubeletX509Authentication,
Convert_v1beta1_SerializedNodeConfigSource_To_kubeletconfig_SerializedNodeConfigSource,
Convert_kubeletconfig_SerializedNodeConfigSource_To_v1beta1_SerializedNodeConfigSource,
)
}
@ -453,3 +455,23 @@ func autoConvert_kubeletconfig_KubeletX509Authentication_To_v1beta1_KubeletX509A
func Convert_kubeletconfig_KubeletX509Authentication_To_v1beta1_KubeletX509Authentication(in *kubeletconfig.KubeletX509Authentication, out *KubeletX509Authentication, s conversion.Scope) error {
return autoConvert_kubeletconfig_KubeletX509Authentication_To_v1beta1_KubeletX509Authentication(in, out, s)
}
func autoConvert_v1beta1_SerializedNodeConfigSource_To_kubeletconfig_SerializedNodeConfigSource(in *SerializedNodeConfigSource, out *kubeletconfig.SerializedNodeConfigSource, s conversion.Scope) error {
out.Source = in.Source
return nil
}
// Convert_v1beta1_SerializedNodeConfigSource_To_kubeletconfig_SerializedNodeConfigSource is an autogenerated conversion function.
func Convert_v1beta1_SerializedNodeConfigSource_To_kubeletconfig_SerializedNodeConfigSource(in *SerializedNodeConfigSource, out *kubeletconfig.SerializedNodeConfigSource, s conversion.Scope) error {
return autoConvert_v1beta1_SerializedNodeConfigSource_To_kubeletconfig_SerializedNodeConfigSource(in, out, s)
}
func autoConvert_kubeletconfig_SerializedNodeConfigSource_To_v1beta1_SerializedNodeConfigSource(in *kubeletconfig.SerializedNodeConfigSource, out *SerializedNodeConfigSource, s conversion.Scope) error {
out.Source = in.Source
return nil
}
// Convert_kubeletconfig_SerializedNodeConfigSource_To_v1beta1_SerializedNodeConfigSource is an autogenerated conversion function.
func Convert_kubeletconfig_SerializedNodeConfigSource_To_v1beta1_SerializedNodeConfigSource(in *kubeletconfig.SerializedNodeConfigSource, out *SerializedNodeConfigSource, s conversion.Scope) error {
return autoConvert_kubeletconfig_SerializedNodeConfigSource_To_v1beta1_SerializedNodeConfigSource(in, out, s)
}

View File

@ -426,3 +426,29 @@ func (in *KubeletX509Authentication) DeepCopy() *KubeletX509Authentication {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SerializedNodeConfigSource) DeepCopyInto(out *SerializedNodeConfigSource) {
*out = *in
out.TypeMeta = in.TypeMeta
in.Source.DeepCopyInto(&out.Source)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SerializedNodeConfigSource.
func (in *SerializedNodeConfigSource) DeepCopy() *SerializedNodeConfigSource {
if in == nil {
return nil
}
out := new(SerializedNodeConfigSource)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *SerializedNodeConfigSource) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@ -246,3 +246,29 @@ func (in *KubeletX509Authentication) DeepCopy() *KubeletX509Authentication {
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SerializedNodeConfigSource) DeepCopyInto(out *SerializedNodeConfigSource) {
*out = *in
out.TypeMeta = in.TypeMeta
in.Source.DeepCopyInto(&out.Source)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SerializedNodeConfigSource.
func (in *SerializedNodeConfigSource) DeepCopy() *SerializedNodeConfigSource {
if in == nil {
return nil
}
out := new(SerializedNodeConfigSource)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *SerializedNodeConfigSource) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@ -19,12 +19,12 @@ go_library(
"//pkg/kubelet/kubeletconfig/checkpoint:go_default_library",
"//pkg/kubelet/kubeletconfig/checkpoint/store:go_default_library",
"//pkg/kubelet/kubeletconfig/status:go_default_library",
"//pkg/kubelet/kubeletconfig/util/equal:go_default_library",
"//pkg/kubelet/kubeletconfig/util/log:go_default_library",
"//pkg/kubelet/kubeletconfig/util/panic:go_default_library",
"//pkg/util/filesystem:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",

View File

@ -31,7 +31,9 @@ go_library(
],
importpath = "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/checkpoint",
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/kubelet/apis/kubeletconfig:go_default_library",
"//pkg/kubelet/apis/kubeletconfig/scheme:go_default_library",
"//pkg/kubelet/apis/kubeletconfig/v1beta1:go_default_library",
"//pkg/kubelet/kubeletconfig/status:go_default_library",
"//pkg/kubelet/kubeletconfig/util/codec:go_default_library",
"//pkg/kubelet/kubeletconfig/util/log:go_default_library",

View File

@ -24,7 +24,9 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/pkg/api/legacyscheme"
kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig"
"k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/scheme"
kubeletconfigv1beta1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1beta1"
"k8s.io/kubernetes/pkg/kubelet/kubeletconfig/status"
utilcodec "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/codec"
utillog "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/log"
@ -46,6 +48,8 @@ type Payload interface {
type RemoteConfigSource interface {
// UID returns a globally unique identifier of the source described by the remote config source object
UID() string
// 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, e.g. its SelfLink
APIPath() string
// Download downloads the remote config source object returns a Payload backed by the object,
@ -60,25 +64,18 @@ type RemoteConfigSource interface {
object() interface{}
}
// NewRemoteConfigSource constructs a RemoteConfigSource from a v1/NodeConfigSource object, or returns
// a sanitized failure reason and an error if the `source` is blatantly invalid.
// 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) {
// exactly one subfield of the config source must be non-nil, toady ConfigMapRef is the only reference
if source.ConfigMapRef == nil {
// 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, today ConfigMap is the only reference subfield
if source.ConfigMap == nil {
return nil, status.FailSyncReasonAllNilSubfields, fmt.Errorf("%s, NodeConfigSource was: %#v", status.FailSyncReasonAllNilSubfields, source)
}
// validate the NodeConfigSource:
// at this point we know we're using the ConfigMapRef subfield
ref := source.ConfigMapRef
// name, namespace, and UID must all be non-empty for ConfigMapRef
if ref.Name == "" || ref.Namespace == "" || string(ref.UID) == "" {
return nil, status.FailSyncReasonPartialObjectReference, fmt.Errorf("%s, ObjectReference was: %#v", status.FailSyncReasonPartialObjectReference, ref)
}
return &remoteConfigMap{source}, "", nil
}
@ -86,21 +83,25 @@ func NewRemoteConfigSource(source *apiv1.NodeConfigSource) (RemoteConfigSource,
// e.g. the metadata stored by checkpoint/store/fsstore.go
func DecodeRemoteConfigSource(data []byte) (RemoteConfigSource, error) {
// decode the remote config source
obj, err := runtime.Decode(legacyscheme.Codecs.UniversalDecoder(), data)
_, codecs, err := scheme.NewSchemeAndCodecs()
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 apiv1.NodeConfigSource,
// 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
// convert it to the external NodeConfigSource type, so we're consistently working with the external type outside of the on-disk representation
cs := &apiv1.NodeConfigSource{}
err = legacyscheme.Scheme.Convert(obj, cs, nil)
if err != nil {
return nil, fmt.Errorf("failed to convert decoded object into a v1 NodeConfigSource, error: %v", err)
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")
}
source, _, err := NewRemoteConfigSource(cs)
// we use the v1.NodeConfigSource type on internal and external, so no need to convert to external here
source, _, err := NewRemoteConfigSource(&cs.Source)
return source, err
}
@ -121,32 +122,36 @@ type remoteConfigMap struct {
var _ RemoteConfigSource = (*remoteConfigMap)(nil)
func (r *remoteConfigMap) UID() string {
return string(r.source.ConfigMapRef.UID)
return string(r.source.ConfigMap.UID)
}
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.ConfigMapRef
ref := r.source.ConfigMap
return fmt.Sprintf(configMapAPIPathFmt, ref.Namespace, ref.Name)
}
func (r *remoteConfigMap) Download(client clientset.Interface) (Payload, string, error) {
var reason string
uid := string(r.source.ConfigMapRef.UID)
uid := string(r.source.ConfigMap.UID)
utillog.Infof("attempting to download ConfigMap with UID %q", uid)
// get the ConfigMap via namespace/name, there doesn't seem to be a way to get it by UID
cm, err := client.CoreV1().ConfigMaps(r.source.ConfigMapRef.Namespace).Get(r.source.ConfigMapRef.Name, metav1.GetOptions{})
cm, err := client.CoreV1().ConfigMaps(r.source.ConfigMap.Namespace).Get(r.source.ConfigMap.Name, metav1.GetOptions{})
if err != nil {
reason = fmt.Sprintf(status.FailSyncReasonDownloadFmt, r.APIPath())
return nil, reason, fmt.Errorf("%s, error: %v", reason, err)
}
// ensure that UID matches the UID on the reference, the ObjectReference must be unambiguous
if r.source.ConfigMapRef.UID != cm.UID {
reason = fmt.Sprintf(status.FailSyncReasonUIDMismatchFmt, r.source.ConfigMapRef.UID, r.APIPath(), cm.UID)
// ensure that UID matches the UID on the source
if r.source.ConfigMap.UID != cm.UID {
reason = fmt.Sprintf(status.FailSyncReasonUIDMismatchFmt, r.source.ConfigMap.UID, r.APIPath(), cm.UID)
return nil, reason, fmt.Errorf(reason)
}
@ -161,11 +166,12 @@ func (r *remoteConfigMap) Download(client clientset.Interface) (Payload, string,
}
func (r *remoteConfigMap) Encode() ([]byte, error) {
encoder, err := utilcodec.NewYAMLEncoder(apiv1.GroupName)
encoder, err := utilcodec.NewKubeletconfigYAMLEncoder(kubeletconfigv1beta1.SchemeGroupVersion)
if err != nil {
return nil, err
}
data, err := runtime.Encode(encoder, r.source)
data, err := runtime.Encode(encoder, &kubeletconfigv1beta1.SerializedNodeConfigSource{Source: *r.source})
if err != nil {
return nil, err
}

View File

@ -36,33 +36,30 @@ func TestNewRemoteConfigSource(t *testing.T) {
expect RemoteConfigSource
err string
}{
// all NodeConfigSource subfields nil
{"all NodeConfigSource subfields nil",
&apiv1.NodeConfigSource{}, nil, "exactly one subfield must be non-nil"},
{"ConfigMapRef: empty name, namespace, and UID",
&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{}}, nil, "invalid ObjectReference"},
// ConfigMapRef: empty name and namespace
{"ConfigMapRef: empty name and namespace",
&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{UID: "uid"}}, nil, "invalid ObjectReference"},
// ConfigMapRef: empty name and UID
{"ConfigMapRef: empty name and UID",
&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Namespace: "namespace"}}, nil, "invalid ObjectReference"},
// ConfigMapRef: empty namespace and UID
{"ConfigMapRef: empty namespace and UID",
&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: "name"}}, nil, "invalid ObjectReference"},
// ConfigMapRef: empty UID
{"ConfigMapRef: empty namespace and UID",
&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: "name", Namespace: "namespace"}}, nil, "invalid ObjectReference"},
// ConfigMapRef: empty namespace
{"ConfigMapRef: empty namespace and UID",
&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: "name", UID: "uid"}}, nil, "invalid ObjectReference"},
// ConfigMapRef: empty name
{"ConfigMapRef: empty namespace and UID",
&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Namespace: "namespace", UID: "uid"}}, nil, "invalid ObjectReference"},
// ConfigMapRef: valid reference
{"ConfigMapRef: valid reference",
&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: "name", Namespace: "namespace", UID: "uid"}},
&remoteConfigMap{&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: "name", Namespace: "namespace", UID: "uid"}}}, ""},
{
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 {
@ -82,7 +79,12 @@ func TestNewRemoteConfigSource(t *testing.T) {
func TestRemoteConfigMapUID(t *testing.T) {
const expect = "uid"
source, _, err := NewRemoteConfigSource(&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: "name", Namespace: "namespace", UID: expect}})
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)
}
@ -93,14 +95,22 @@ func TestRemoteConfigMapUID(t *testing.T) {
}
func TestRemoteConfigMapAPIPath(t *testing.T) {
const namespace = "namespace"
const name = "name"
source, _, err := NewRemoteConfigSource(&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: name, Namespace: namespace, UID: "uid"}})
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)
}
@ -133,18 +143,39 @@ func TestRemoteConfigMapDownload(t *testing.T) {
expect Payload
err string
}{
// object doesn't exist
{"object doesn't exist",
makeSource(&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: "bogus", Namespace: "namespace", UID: "bogus"}}),
nil, "not found"},
// UID of downloaded object doesn't match UID of referent found via namespace/name
{"UID is incorrect for namespace/name",
makeSource(&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: "name", Namespace: "namespace", UID: "bogus"}}),
nil, "does not match"},
// successful download
{"object exists and reference is correct",
makeSource(&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: "name", Namespace: "namespace", UID: "uid"}}),
payload, ""},
{
desc: "object doesn't exist",
source: makeSource(&apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
Name: "bogus",
Namespace: "namespace",
UID: "bogus",
KubeletConfigKey: "kubelet",
}}),
expect: nil,
err: "not found",
},
{
desc: "UID is incorrect for namespace/name",
source: makeSource(&apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
Name: "name",
Namespace: "namespace",
UID: "bogus",
KubeletConfigKey: "kubelet",
}}),
expect: nil,
err: "does not match",
},
{
desc: "object exists and reference is correct",
source: makeSource(&apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
Name: "name",
Namespace: "namespace",
UID: "uid",
KubeletConfigKey: "kubelet",
}}),
expect: payload,
err: "",
},
}
for _, c := range cases {
@ -173,10 +204,12 @@ func TestEqualRemoteConfigSources(t *testing.T) {
{"a nil", nil, &remoteConfigMap{}, false},
{"b nil", &remoteConfigMap{}, nil, false},
{"neither nil, equal", &remoteConfigMap{}, &remoteConfigMap{}, true},
{"neither nil, not equal",
&remoteConfigMap{&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: "a"}}},
&remoteConfigMap{},
false},
{
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 {

View File

@ -35,7 +35,6 @@ const (
lastKnownGoodFile = "last-known-good"
checkpointsDir = "checkpoints"
kubeletKey = "kubelet" // TODO(mtaufen): eventually the API will have a way to parameterize the kubelet file name, and then we can remove this
)
// fsStore is for tracking checkpoints in the local filesystem, implements Store
@ -101,7 +100,7 @@ func (s *fsStore) Load(source checkpoint.RemoteConfigSource) (*kubeletconfig.Kub
}
// load the kubelet config file
utillog.Infof("loading kubelet configuration checkpoint for source %s", sourceFmt)
loader, err := configfiles.NewFsLoader(s.fs, filepath.Join(s.checkpointPath(source.UID()), kubeletKey))
loader, err := configfiles.NewFsLoader(s.fs, filepath.Join(s.checkpointPath(source.UID()), source.KubeletFilename()))
if err != nil {
return nil, err
}

View File

@ -125,7 +125,12 @@ func TestFsStoreExists(t *testing.T) {
for _, c := range cases {
t.Run(c.desc, func(t *testing.T) {
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{
ConfigMapRef: &apiv1.ObjectReference{Name: "name", Namespace: "namespace", UID: c.uid}})
ConfigMap: &apiv1.ConfigMapNodeConfigSource{
Name: "name",
Namespace: "namespace",
UID: c.uid,
KubeletConfigKey: "kubelet",
}})
if err != nil {
t.Fatalf("error constructing remote config source: %v", err)
}
@ -214,7 +219,10 @@ func TestFsStoreLoad(t *testing.T) {
t.Fatalf("error encoding KubeletConfiguration: %v", err)
}
// construct a payload that contains the kubeletconfig
const uid = "uid"
const (
uid = "uid"
kubeletKey = "kubelet"
)
p, err := checkpoint.NewConfigMapPayload(&apiv1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{UID: types.UID(uid)},
Data: map[string]string{
@ -241,7 +249,12 @@ func TestFsStoreLoad(t *testing.T) {
for _, c := range cases {
t.Run(c.desc, func(t *testing.T) {
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{
ConfigMapRef: &apiv1.ObjectReference{Name: "name", Namespace: "namespace", UID: c.uid}})
ConfigMap: &apiv1.ConfigMapNodeConfigSource{
Name: "name",
Namespace: "namespace",
UID: c.uid,
KubeletConfigKey: kubeletKey,
}})
if err != nil {
t.Fatalf("error constructing remote config source: %v", err)
}
@ -291,7 +304,12 @@ func TestFsStoreCurrent(t *testing.T) {
}
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{
ConfigMapRef: &apiv1.ObjectReference{Name: "name", Namespace: "namespace", UID: "uid"}})
ConfigMap: &apiv1.ConfigMapNodeConfigSource{
Name: "name",
Namespace: "namespace",
UID: "uid",
KubeletConfigKey: "kubelet",
}})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -329,7 +347,12 @@ func TestFsStoreLastKnownGood(t *testing.T) {
}
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{
ConfigMapRef: &apiv1.ObjectReference{Name: "name", Namespace: "namespace", UID: "uid"}})
ConfigMap: &apiv1.ConfigMapNodeConfigSource{
Name: "name",
Namespace: "namespace",
UID: "uid",
KubeletConfigKey: "kubelet",
}})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -367,15 +390,21 @@ func TestFsStoreSetCurrent(t *testing.T) {
}
const uid = "uid"
expect := fmt.Sprintf(`apiVersion: v1
configMapRef:
name: name
namespace: namespace
uid: %s
kind: NodeConfigSource
expect := fmt.Sprintf(`apiVersion: kubelet.config.k8s.io/v1beta1
kind: SerializedNodeConfigSource
source:
configMap:
kubeletConfigKey: kubelet
name: name
namespace: namespace
uid: %s
`, uid)
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{
Name: "name", Namespace: "namespace", UID: types.UID(uid)}})
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
Name: "name",
Namespace: "namespace",
UID: types.UID(uid),
KubeletConfigKey: "kubelet",
}})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -399,15 +428,21 @@ func TestFsStoreSetLastKnownGood(t *testing.T) {
}
const uid = "uid"
expect := fmt.Sprintf(`apiVersion: v1
configMapRef:
name: name
namespace: namespace
uid: %s
kind: NodeConfigSource
expect := fmt.Sprintf(`apiVersion: kubelet.config.k8s.io/v1beta1
kind: SerializedNodeConfigSource
source:
configMap:
kubeletConfigKey: kubelet
name: name
namespace: namespace
uid: %s
`, uid)
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{
Name: "name", Namespace: "namespace", UID: types.UID(uid)}})
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
Name: "name",
Namespace: "namespace",
UID: types.UID(uid),
KubeletConfigKey: "kubelet",
}})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -430,11 +465,21 @@ func TestFsStoreReset(t *testing.T) {
t.Fatalf("error constructing store: %v", err)
}
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: "name", Namespace: "namespace", UID: "uid"}})
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{ConfigMapRef: &apiv1.ObjectReference{Name: "other-name", Namespace: "namespace", UID: "other-uid"}})
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)
}
@ -498,7 +543,12 @@ func TestFsStoreReadRemoteConfigSource(t *testing.T) {
}
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{
ConfigMapRef: &apiv1.ObjectReference{Name: "name", Namespace: "namespace", UID: "uid"}})
ConfigMap: &apiv1.ConfigMapNodeConfigSource{
Name: "name",
Namespace: "namespace",
UID: "uid",
KubeletConfigKey: "kubelet",
}})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@ -534,7 +584,12 @@ func TestFsStoreWriteRemoteConfigSource(t *testing.T) {
t.Fatalf("error constructing store: %v", err)
}
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: "name", Namespace: "namespace", UID: "uid"}})
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)
}

View File

@ -26,11 +26,21 @@ import (
)
func TestReset(t *testing.T) {
source, _, err := checkpoint.NewRemoteConfigSource(&apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{Name: "name", Namespace: "namespace", UID: "uid"}})
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{ConfigMapRef: &apiv1.ObjectReference{Name: "other-name", Namespace: "namespace", UID: "other-uid"}})
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)
}

View File

@ -65,11 +65,11 @@ const (
FailSyncReasonFmt = "failed to sync, reason: %s"
// FailSyncReasonAllNilSubfields is used when no subfields are set
FailSyncReasonAllNilSubfields = "invalid NodeConfigSource, exactly one subfield must be non-nil, but all were nil"
// FailSyncReasonPartialObjectReference is used when some required subfields remain unset
FailSyncReasonPartialObjectReference = "invalid ObjectReference, all of UID, Name, and Namespace must be specified"
// FailSyncReasonPartialConfigMapSource is used when some required subfields remain unset
FailSyncReasonPartialConfigMapSource = "invalid ConfigSource.ConfigMap, all of UID, Name, Namespace, and KubeletConfigKey must be specified"
// FailSyncReasonUIDMismatchFmt is used when there is a UID mismatch between the referenced and downloaded ConfigMaps,
// this can happen because objects must be downloaded by namespace/name, rather than by UID
FailSyncReasonUIDMismatchFmt = "invalid ConfigSource.ConfigMapRef.UID: %s does not match %s.UID: %s"
FailSyncReasonUIDMismatchFmt = "invalid ConfigSource.ConfigMap.UID: %s does not match %s.UID: %s"
// FailSyncReasonDownloadFmt is used when the download fails, e.g. due to network issues
FailSyncReasonDownloadFmt = "failed to download: %s"
// FailSyncReasonInformer is used when the informer fails to report the Node object

View File

@ -32,7 +32,7 @@ import (
// EncodeKubeletConfig encodes an internal KubeletConfiguration to an external YAML representation
func EncodeKubeletConfig(internal *kubeletconfig.KubeletConfiguration, targetVersion schema.GroupVersion) ([]byte, error) {
encoder, err := newKubeletConfigYAMLEncoder(targetVersion)
encoder, err := NewKubeletconfigYAMLEncoder(targetVersion)
if err != nil {
return nil, err
}
@ -44,8 +44,8 @@ func EncodeKubeletConfig(internal *kubeletconfig.KubeletConfiguration, targetVer
return data, nil
}
// newKubeletConfigYAMLEncoder returns an encoder that can write a KubeletConfig to YAML
func newKubeletConfigYAMLEncoder(targetVersion schema.GroupVersion) (runtime.Encoder, error) {
// NewKubeletconfigYAMLEncoder returns an encoder that can write objects in the kubeletconfig API group to YAML
func NewKubeletconfigYAMLEncoder(targetVersion schema.GroupVersion) (runtime.Encoder, error) {
_, codecs, err := scheme.NewSchemeAndCodecs()
if err != nil {
return nil, err

View File

@ -18,33 +18,6 @@ package equal
import apiv1 "k8s.io/api/core/v1"
// ConfigSourceEq returns true if the two config sources are semantically equivalent in the context of dynamic config
func ConfigSourceEq(a, b *apiv1.NodeConfigSource) bool {
if a == b {
return true
} else if a == nil || b == nil {
// not equal, and one is nil
return false
}
// check equality of config source subifelds
if a.ConfigMapRef != b.ConfigMapRef {
return ObjectRefEq(a.ConfigMapRef, b.ConfigMapRef)
}
// all internal subfields of the config source are equal
return true
}
// ObjectRefEq returns true if the two object references are semantically equivalent in the context of dynamic config
func ObjectRefEq(a, b *apiv1.ObjectReference) bool {
if a == b {
return true
} else if a == nil || b == nil {
// not equal, and one is nil
return false
}
return a.UID == b.UID && a.Namespace == b.Namespace && a.Name == b.Name
}
// KubeletConfigOkEq returns true if the two conditions are semantically equivalent in the context of dynamic config
func KubeletConfigOkEq(a, b *apiv1.NodeCondition) bool {
return a.Message == b.Message && a.Reason == b.Reason && a.Status == b.Status

View File

@ -21,13 +21,13 @@ import (
"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"
kuberuntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
utilequal "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/equal"
utillog "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/log"
)
@ -94,7 +94,7 @@ func (cc *Controller) onUpdateNodeEvent(oldObj interface{}, newObj interface{})
utillog.Errorf("failed to cast old object to Node, couldn't handle event")
return
}
if !utilequal.ConfigSourceEq(oldNode.Spec.ConfigSource, newNode.Spec.ConfigSource) {
if !apiequality.Semantic.DeepEqual(oldNode.Spec.ConfigSource, newNode.Spec.ConfigSource) {
cc.pokeConfigSourceWorker()
}
}

View File

@ -91,9 +91,19 @@ func Test_nodePlugin_Admit(t *testing.T) {
mynodeObjMeta = metav1.ObjectMeta{Name: "mynode"}
mynodeObj = &api.Node{ObjectMeta: mynodeObjMeta}
mynodeObjConfigA = &api.Node{ObjectMeta: mynodeObjMeta, Spec: api.NodeSpec{ConfigSource: &api.NodeConfigSource{
ConfigMapRef: &api.ObjectReference{Name: "foo", Namespace: "bar", UID: "fooUID"}}}}
ConfigMap: &api.ConfigMapNodeConfigSource{
Name: "foo",
Namespace: "bar",
UID: "fooUID",
KubeletConfigKey: "kubelet",
}}}}
mynodeObjConfigB = &api.Node{ObjectMeta: mynodeObjMeta, Spec: api.NodeSpec{ConfigSource: &api.NodeConfigSource{
ConfigMapRef: &api.ObjectReference{Name: "qux", Namespace: "bar", UID: "quxUID"}}}}
ConfigMap: &api.ConfigMapNodeConfigSource{
Name: "qux",
Namespace: "bar",
UID: "quxUID",
KubeletConfigKey: "kubelet",
}}}}
othernodeObj = &api.Node{ObjectMeta: metav1.ObjectMeta{Name: "othernode"}}
mymirrorpod = makeTestPod("ns", "mymirrorpod", "mynode", true)

View File

@ -408,7 +408,7 @@ func (g *Graph) DeleteVolumeAttachment(name string) {
g.deleteVertex_locked(vaVertexType, "", name)
}
// SetNodeConfigMap sets up edges for the Node.Spec.ConfigSource.ConfigMapRef relationship:
// SetNodeConfigMap sets up edges for the Node.Spec.ConfigSource.ConfigMap relationship:
//
// configmap -> node
func (g *Graph) SetNodeConfigMap(nodeName, configMapName, configMapNamespace string) {

View File

@ -84,19 +84,19 @@ func (g *graphPopulator) updateNode(oldObj, obj interface{}) {
oldNode = oldObj.(*api.Node)
}
// we only set up rules for ConfigMapRef today, because that is the only reference type
// we only set up rules for ConfigMap today, because that is the only reference type
var name, namespace string
if source := node.Spec.ConfigSource; source != nil && source.ConfigMapRef != nil {
name = source.ConfigMapRef.Name
namespace = source.ConfigMapRef.Namespace
if source := node.Spec.ConfigSource; source != nil && source.ConfigMap != nil {
name = source.ConfigMap.Name
namespace = source.ConfigMap.Namespace
}
var oldName, oldNamespace string
if oldNode != nil {
if oldSource := oldNode.Spec.ConfigSource; oldSource != nil && oldSource.ConfigMapRef != nil {
oldName = oldSource.ConfigMapRef.Name
oldNamespace = oldSource.ConfigMapRef.Namespace
if oldSource := oldNode.Spec.ConfigSource; oldSource != nil && oldSource.ConfigMap != nil {
oldName = oldSource.ConfigMap.Name
oldNamespace = oldSource.ConfigMap.Namespace
}
}

View File

@ -753,10 +753,11 @@ func generate(opts sampleDataOpts) ([]*api.Node, []*api.Pod, []*api.PersistentVo
ObjectMeta: metav1.ObjectMeta{Name: nodeName},
Spec: api.NodeSpec{
ConfigSource: &api.NodeConfigSource{
ConfigMapRef: &api.ObjectReference{
Name: name,
Namespace: "ns0",
UID: types.UID(fmt.Sprintf("ns0-%s", name)),
ConfigMap: &api.ConfigMapNodeConfigSource{
Name: name,
Namespace: "ns0",
UID: types.UID(fmt.Sprintf("ns0-%s", name)),
KubeletConfigKey: "kubelet",
},
},
},

File diff suppressed because it is too large Load Diff

View File

@ -439,6 +439,34 @@ message ConfigMapList {
repeated ConfigMap items = 2;
}
// ConfigMapNodeConfigSource contains the information to reference a ConfigMap as a config source for the Node.
message ConfigMapNodeConfigSource {
// Namespace is the metadata.namespace of the referenced ConfigMap.
// This field is required in all cases.
optional string namespace = 1;
// Name is the metadata.name of the referenced ConfigMap.
// This field is required in all cases.
optional string name = 2;
// UID is the metadata.UID of the referenced ConfigMap.
// This field is currently reqired in Node.Spec.
// TODO(#61643): This field will be forbidden in Node.Spec when #61643 is resolved.
// TODO(#56896): This field will be required in Node.Status when #56896 is resolved.
// +optional
optional string uid = 3;
// ResourceVersion is the metadata.ResourceVersion of the referenced ConfigMap.
// This field is forbidden in Node.Spec.
// TODO(#56896): This field will be required in Node.Status when #56896 is resolved.
// +optional
optional string resourceVersion = 4;
// KubeletConfigKey declares which key of the referenced ConfigMap corresponds to the KubeletConfiguration structure
// This field is required in all cases.
optional string kubeletConfigKey = 5;
}
// Adapts a ConfigMap into a projected volume.
//
// The contents of the target ConfigMap's Data field will be presented in a
@ -1815,7 +1843,8 @@ message NodeCondition {
// NodeConfigSource specifies a source of node configuration. Exactly one subfield (excluding metadata) must be non-nil.
message NodeConfigSource {
optional ObjectReference configMapRef = 1;
// ConfigMap is a reference to a Node's ConfigMap
optional ConfigMapNodeConfigSource configMap = 2;
}
// NodeDaemonEndpoints lists ports opened by daemons running on the Node.

View File

@ -57,7 +57,6 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&Endpoints{},
&EndpointsList{},
&Node{},
&NodeConfigSource{},
&NodeList{},
&NodeProxyOptions{},
&Binding{},

View File

@ -3640,12 +3640,49 @@ type NodeSpec struct {
DoNotUse_ExternalID string `json:"externalID,omitempty" protobuf:"bytes,2,opt,name=externalID"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// NodeConfigSource specifies a source of node configuration. Exactly one subfield (excluding metadata) must be non-nil.
type NodeConfigSource struct {
metav1.TypeMeta `json:",inline"`
ConfigMapRef *ObjectReference `json:"configMapRef,omitempty" protobuf:"bytes,1,opt,name=configMapRef"`
// For historical context, regarding the below kind, apiVersion, and configMapRef deprecation tags:
// 1. kind/apiVersion were used by the kubelet to persist this struct to disk (they had no protobuf tags)
// 2. configMapRef and proto tag 1 were used by the API to refer to a configmap,
// but used a generic ObjectReference type that didn't really have the fields we needed
// All uses/persistence of the NodeConfigSource struct prior to 1.11 were gated by alpha feature flags,
// so there was no persisted data for these fields that needed to be migrated/handled.
// +k8s:deprecated=kind
// +k8s:deprecated=apiVersion
// +k8s:deprecated=configMapRef,protobuf=1
// ConfigMap is a reference to a Node's ConfigMap
ConfigMap *ConfigMapNodeConfigSource `json:"configMap,omitempty" protobuf:"bytes,2,opt,name=configMap"`
}
// ConfigMapNodeConfigSource contains the information to reference a ConfigMap as a config source for the Node.
type ConfigMapNodeConfigSource struct {
// Namespace is the metadata.namespace of the referenced ConfigMap.
// This field is required in all cases.
Namespace string `json:"namespace" protobuf:"bytes,1,opt,name=namespace"`
// Name is the metadata.name of the referenced ConfigMap.
// This field is required in all cases.
Name string `json:"name" protobuf:"bytes,2,opt,name=name"`
// UID is the metadata.UID of the referenced ConfigMap.
// This field is currently reqired in Node.Spec.
// TODO(#61643): This field will be forbidden in Node.Spec when #61643 is resolved.
// TODO(#56896): This field will be required in Node.Status when #56896 is resolved.
// +optional
UID types.UID `json:"uid,omitempty" protobuf:"bytes,3,opt,name=uid"`
// ResourceVersion is the metadata.ResourceVersion of the referenced ConfigMap.
// This field is forbidden in Node.Spec.
// TODO(#56896): This field will be required in Node.Status when #56896 is resolved.
// +optional
ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,4,opt,name=resourceVersion"`
// KubeletConfigKey declares which key of the referenced ConfigMap corresponds to the KubeletConfiguration structure
// This field is required in all cases.
KubeletConfigKey string `json:"kubeletConfigKey" protobuf:"bytes,5,opt,name=kubeletConfigKey"`
}
// DaemonEndpoint contains information about a single Daemon endpoint.

View File

@ -262,6 +262,19 @@ func (ConfigMapList) SwaggerDoc() map[string]string {
return map_ConfigMapList
}
var map_ConfigMapNodeConfigSource = map[string]string{
"": "ConfigMapNodeConfigSource contains the information to reference a ConfigMap as a config source for the Node.",
"namespace": "Namespace is the metadata.namespace of the referenced ConfigMap. This field is required in all cases.",
"name": "Name is the metadata.name of the referenced ConfigMap. This field is required in all cases.",
"uid": "UID is the metadata.UID of the referenced ConfigMap. This field is currently reqired in Node.Spec.",
"resourceVersion": "ResourceVersion is the metadata.ResourceVersion of the referenced ConfigMap. This field is forbidden in Node.Spec.",
"kubeletConfigKey": "KubeletConfigKey declares which key of the referenced ConfigMap corresponds to the KubeletConfiguration structure This field is required in all cases.",
}
func (ConfigMapNodeConfigSource) SwaggerDoc() map[string]string {
return map_ConfigMapNodeConfigSource
}
var map_ConfigMapProjection = map[string]string{
"": "Adapts a ConfigMap into a projected volume.\n\nThe contents of the target ConfigMap's Data field will be presented in a projected volume as files using the keys in the Data field as the file names, unless the items element is populated with specific mappings of keys to paths. Note that this is identical to a configmap volume source without the default mode.",
"items": "If unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'.",
@ -969,7 +982,8 @@ func (NodeCondition) SwaggerDoc() map[string]string {
}
var map_NodeConfigSource = map[string]string{
"": "NodeConfigSource specifies a source of node configuration. Exactly one subfield (excluding metadata) must be non-nil.",
"": "NodeConfigSource specifies a source of node configuration. Exactly one subfield (excluding metadata) must be non-nil.",
"configMap": "ConfigMap is a reference to a Node's ConfigMap",
}
func (NodeConfigSource) SwaggerDoc() map[string]string {

View File

@ -631,6 +631,22 @@ func (in *ConfigMapList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConfigMapNodeConfigSource) DeepCopyInto(out *ConfigMapNodeConfigSource) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapNodeConfigSource.
func (in *ConfigMapNodeConfigSource) DeepCopy() *ConfigMapNodeConfigSource {
if in == nil {
return nil
}
out := new(ConfigMapNodeConfigSource)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ConfigMapProjection) DeepCopyInto(out *ConfigMapProjection) {
*out = *in
@ -2360,13 +2376,12 @@ func (in *NodeCondition) DeepCopy() *NodeCondition {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NodeConfigSource) DeepCopyInto(out *NodeConfigSource) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.ConfigMapRef != nil {
in, out := &in.ConfigMapRef, &out.ConfigMapRef
if in.ConfigMap != nil {
in, out := &in.ConfigMap, &out.ConfigMap
if *in == nil {
*out = nil
} else {
*out = new(ObjectReference)
*out = new(ConfigMapNodeConfigSource)
**out = **in
}
}
@ -2383,14 +2398,6 @@ func (in *NodeConfigSource) DeepCopy() *NodeConfigSource {
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *NodeConfigSource) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NodeDaemonEndpoints) DeepCopyInto(out *NodeDaemonEndpoints) {
*out = *in

View File

@ -84,10 +84,12 @@ var _ = framework.KubeDescribe("DynamicKubeletConfiguration [Feature:DynamicKube
// were initially set via the locally provisioned configuration.
// This is the same strategy several other e2e node tests use.
setAndTestKubeletConfigState(f, &configState{desc: "reset to original values",
configSource: &apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{
UID: originalConfigMap.UID,
Namespace: originalConfigMap.Namespace,
Name: originalConfigMap.Name}},
configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
UID: originalConfigMap.UID,
Namespace: originalConfigMap.Namespace,
Name: originalConfigMap.Name,
KubeletConfigKey: "kubelet",
}},
expectConfigOk: &apiv1.NodeCondition{Type: apiv1.NodeKubeletConfigOk, Status: apiv1.ConditionTrue,
Message: fmt.Sprintf(status.CurRemoteMessageFmt, configMapAPIPath(originalConfigMap)),
Reason: status.CurRemoteOkayReason},
@ -123,8 +125,8 @@ var _ = framework.KubeDescribe("DynamicKubeletConfiguration [Feature:DynamicKube
framework.ExpectNoError(err)
states := []configState{
// Node.Spec.ConfigSource is nil
{desc: "Node.Spec.ConfigSource is nil",
{
desc: "Node.Spec.ConfigSource is nil",
configSource: nil,
expectConfigOk: &apiv1.NodeCondition{Type: apiv1.NodeKubeletConfigOk, Status: apiv1.ConditionTrue,
Message: status.CurLocalMessage,
@ -132,65 +134,107 @@ var _ = framework.KubeDescribe("DynamicKubeletConfiguration [Feature:DynamicKube
expectConfig: nil,
event: true,
},
// Node.Spec.ConfigSource has all nil subfields
{desc: "Node.Spec.ConfigSource has all nil subfields",
configSource: &apiv1.NodeConfigSource{},
apierr: "exactly one reference subfield must be non-nil",
},
// Node.Spec.ConfigSource.ConfigMapRef is partial
{desc: "Node.Spec.ConfigSource.ConfigMapRef is partial",
configSource: &apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{
UID: "foo",
Name: "bar"}}, // missing Namespace
apierr: "name, namespace, and UID must all be non-empty",
// Node.Spec.ConfigSource.ConfigMap is partial
{desc: "Node.Spec.ConfigSource.ConfigMap is partial",
configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
UID: "foo",
Name: "bar",
KubeletConfigKey: "kubelet",
}}, // missing Namespace
apierr: "spec.configSource.configMap.namespace: Required value: namespace must be set in spec",
},
// Node.Spec.ConfigSource's UID does not align with namespace/name
{desc: "Node.Spec.ConfigSource.ConfigMapRef.UID does not align with Namespace/Name",
configSource: &apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{UID: "foo",
Namespace: correctConfigMap.Namespace,
Name: correctConfigMap.Name}},
{desc: "Node.Spec.ConfigSource.ConfigMap.ResourceVersion is illegally specified",
configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
UID: "foo",
Name: "bar",
Namespace: "baz",
ResourceVersion: "1",
KubeletConfigKey: "kubelet",
}},
apierr: "spec.configSource.configMap.resourceVersion: Forbidden: resourceVersion must not be set in spec",
},
{desc: "Node.Spec.ConfigSource.ConfigMap has invalid namespace",
configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
UID: "foo",
Name: "bar",
Namespace: "../baz",
KubeletConfigKey: "kubelet",
}},
apierr: "spec.configSource.configMap.namespace: Invalid value",
},
{desc: "Node.Spec.ConfigSource.ConfigMap has invalid name",
configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
UID: "foo",
Name: "../bar",
Namespace: "baz",
KubeletConfigKey: "kubelet",
}},
apierr: "spec.configSource.configMap.name: Invalid value",
},
{desc: "Node.Spec.ConfigSource.ConfigMap has invalid kubeletConfigKey",
configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
UID: "foo",
Name: "bar",
Namespace: "baz",
KubeletConfigKey: "../qux",
}},
apierr: "spec.configSource.configMap.kubeletConfigKey: Invalid value",
},
{
desc: "Node.Spec.ConfigSource.ConfigMap.UID does not align with Namespace/Name",
configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
UID: "foo",
Namespace: correctConfigMap.Namespace,
Name: correctConfigMap.Name,
KubeletConfigKey: "kubelet",
}},
expectConfigOk: &apiv1.NodeCondition{Type: apiv1.NodeKubeletConfigOk, Status: apiv1.ConditionFalse,
Message: "",
Reason: fmt.Sprintf(status.FailSyncReasonFmt, fmt.Sprintf(status.FailSyncReasonUIDMismatchFmt, "foo", configMapAPIPath(correctConfigMap), correctConfigMap.UID))},
expectConfig: nil,
event: false,
},
// correct
{desc: "correct",
configSource: &apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{
UID: correctConfigMap.UID,
Namespace: correctConfigMap.Namespace,
Name: correctConfigMap.Name}},
{
desc: "correct",
configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
UID: correctConfigMap.UID,
Namespace: correctConfigMap.Namespace,
Name: correctConfigMap.Name,
KubeletConfigKey: "kubelet",
}},
expectConfigOk: &apiv1.NodeCondition{Type: apiv1.NodeKubeletConfigOk, Status: apiv1.ConditionTrue,
Message: fmt.Sprintf(status.CurRemoteMessageFmt, configMapAPIPath(correctConfigMap)),
Reason: status.CurRemoteOkayReason},
expectConfig: correctKC,
event: true,
},
// fail-parse
{desc: "fail-parse",
configSource: &apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{
UID: failParseConfigMap.UID,
Namespace: failParseConfigMap.Namespace,
Name: failParseConfigMap.Name}},
{
desc: "fail-parse",
configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
UID: failParseConfigMap.UID,
Namespace: failParseConfigMap.Namespace,
Name: failParseConfigMap.Name,
KubeletConfigKey: "kubelet",
}},
expectConfigOk: &apiv1.NodeCondition{Type: apiv1.NodeKubeletConfigOk, Status: apiv1.ConditionFalse,
Message: status.LkgLocalMessage,
Reason: fmt.Sprintf(status.CurFailLoadReasonFmt, configMapAPIPath(failParseConfigMap))},
expectConfig: nil,
event: true,
},
// fail-validate
{desc: "fail-validate",
configSource: &apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{
UID: failValidateConfigMap.UID,
Namespace: failValidateConfigMap.Namespace,
Name: failValidateConfigMap.Name}},
{
desc: "fail-validate",
configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
UID: failValidateConfigMap.UID,
Namespace: failValidateConfigMap.Namespace,
Name: failValidateConfigMap.Name,
KubeletConfigKey: "kubelet",
}},
expectConfigOk: &apiv1.NodeCondition{Type: apiv1.NodeKubeletConfigOk, Status: apiv1.ConditionFalse,
Message: status.LkgLocalMessage,
Reason: fmt.Sprintf(status.CurFailValidateReasonFmt, configMapAPIPath(failValidateConfigMap))},
@ -229,10 +273,12 @@ var _ = framework.KubeDescribe("DynamicKubeletConfiguration [Feature:DynamicKube
states := []configState{
// intended lkg
{desc: "intended last-known-good",
configSource: &apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{
UID: lkgConfigMap.UID,
Namespace: lkgConfigMap.Namespace,
Name: lkgConfigMap.Name}},
configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
UID: lkgConfigMap.UID,
Namespace: lkgConfigMap.Namespace,
Name: lkgConfigMap.Name,
KubeletConfigKey: "kubelet",
}},
expectConfigOk: &apiv1.NodeCondition{Type: apiv1.NodeKubeletConfigOk, Status: apiv1.ConditionTrue,
Message: fmt.Sprintf(status.CurRemoteMessageFmt, configMapAPIPath(lkgConfigMap)),
Reason: status.CurRemoteOkayReason},
@ -242,10 +288,12 @@ var _ = framework.KubeDescribe("DynamicKubeletConfiguration [Feature:DynamicKube
// bad config
{desc: "bad config",
configSource: &apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{
UID: badConfigMap.UID,
Namespace: badConfigMap.Namespace,
Name: badConfigMap.Name}},
configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
UID: badConfigMap.UID,
Namespace: badConfigMap.Namespace,
Name: badConfigMap.Name,
KubeletConfigKey: "kubelet",
}},
expectConfigOk: &apiv1.NodeCondition{Type: apiv1.NodeKubeletConfigOk, Status: apiv1.ConditionFalse,
Message: fmt.Sprintf(status.LkgRemoteMessageFmt, configMapAPIPath(lkgConfigMap)),
Reason: fmt.Sprintf(status.CurFailLoadReasonFmt, configMapAPIPath(badConfigMap))},
@ -259,6 +307,55 @@ var _ = framework.KubeDescribe("DynamicKubeletConfiguration [Feature:DynamicKube
})
})
Context("When a remote config becomes the new last-known-good, and then Node.ConfigSource.ConfigMap.KubeletConfigKey is updated to use a new, bad config", func() {
It("the Kubelet should report a status and configz indicating that it rolled back to the new last-known-good", func() {
const badConfigKey = "bad"
var err error
// we base the "lkg" configmap off of the current configuration
lkgKC := originalKC.DeepCopy()
combinedConfigMap := newKubeletConfigMap("dynamic-kubelet-config-test-combined", lkgKC)
combinedConfigMap.Data[badConfigKey] = "{0xdeadbeef}"
combinedConfigMap, err = f.ClientSet.CoreV1().ConfigMaps("kube-system").Create(combinedConfigMap)
framework.ExpectNoError(err)
states := []configState{
// intended lkg
{desc: "intended last-known-good",
configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
UID: combinedConfigMap.UID,
Namespace: combinedConfigMap.Namespace,
Name: combinedConfigMap.Name,
KubeletConfigKey: "kubelet",
}},
expectConfigOk: &apiv1.NodeCondition{Type: apiv1.NodeKubeletConfigOk, Status: apiv1.ConditionTrue,
Message: fmt.Sprintf(status.CurRemoteMessageFmt, configMapAPIPath(combinedConfigMap)),
Reason: status.CurRemoteOkayReason},
expectConfig: lkgKC,
event: true,
},
// bad config
{desc: "bad config",
configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
UID: combinedConfigMap.UID,
Namespace: combinedConfigMap.Namespace,
Name: combinedConfigMap.Name,
KubeletConfigKey: badConfigKey,
}},
expectConfigOk: &apiv1.NodeCondition{Type: apiv1.NodeKubeletConfigOk, Status: apiv1.ConditionFalse,
// TODO(mtaufen): status should be more informative, and report the key being used
Message: fmt.Sprintf(status.LkgRemoteMessageFmt, configMapAPIPath(combinedConfigMap)),
Reason: fmt.Sprintf(status.CurFailLoadReasonFmt, configMapAPIPath(combinedConfigMap))},
expectConfig: lkgKC,
event: true,
},
}
// wait 12 minutes after setting the first config to ensure it has time to pass the trial duration
testBothDirections(f, &states[0], states[1:], 12*time.Minute)
})
})
// This stress test will help turn up resource leaks across kubelet restarts that can, over time,
// break our ability to dynamically update kubelet config
Context("When changing the configuration 100 times", func() {
@ -280,10 +377,12 @@ var _ = framework.KubeDescribe("DynamicKubeletConfiguration [Feature:DynamicKube
states := []configState{
{desc: "cm1",
configSource: &apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{
UID: cm1.UID,
Namespace: cm1.Namespace,
Name: cm1.Name}},
configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
UID: cm1.UID,
Namespace: cm1.Namespace,
Name: cm1.Name,
KubeletConfigKey: "kubelet",
}},
expectConfigOk: &apiv1.NodeCondition{Type: apiv1.NodeKubeletConfigOk, Status: apiv1.ConditionTrue,
Message: fmt.Sprintf(status.CurRemoteMessageFmt, configMapAPIPath(cm1)),
Reason: status.CurRemoteOkayReason},
@ -292,10 +391,12 @@ var _ = framework.KubeDescribe("DynamicKubeletConfiguration [Feature:DynamicKube
},
{desc: "cm2",
configSource: &apiv1.NodeConfigSource{ConfigMapRef: &apiv1.ObjectReference{
UID: cm2.UID,
Namespace: cm2.Namespace,
Name: cm2.Name}},
configSource: &apiv1.NodeConfigSource{ConfigMap: &apiv1.ConfigMapNodeConfigSource{
UID: cm2.UID,
Namespace: cm2.Namespace,
Name: cm2.Name,
KubeletConfigKey: "kubelet",
}},
expectConfigOk: &apiv1.NodeCondition{Type: apiv1.NodeKubeletConfigOk, Status: apiv1.ConditionTrue,
Message: fmt.Sprintf(status.CurRemoteMessageFmt, configMapAPIPath(cm2)),
Reason: status.CurRemoteOkayReason},
@ -335,7 +436,7 @@ func testBothDirections(f *framework.Framework, first *configState, states []con
}
}
// setAndTestKubeletConfigState tests that after setting the config source, the ConfigOk condition
// setAndTestKubeletConfigState tests that after setting the config source, the KubeletConfigOk condition
// and (if appropriate) configuration exposed via conifgz are as expected.
// The configuration will be converted to the internal type prior to comparison.
func setAndTestKubeletConfigState(f *framework.Framework, state *configState, expectEvent bool) {
@ -479,8 +580,8 @@ func checkEvent(f *framework.Framework, desc string, expect *apiv1.NodeConfigSou
// ensure the message is what we expect (including the resource path)
expectMessage := fmt.Sprintf(controller.EventMessageFmt, controller.LocalConfigMessage)
if expect != nil {
if expect.ConfigMapRef != nil {
expectMessage = fmt.Sprintf(controller.EventMessageFmt, fmt.Sprintf("/api/v1/namespaces/%s/configmaps/%s", expect.ConfigMapRef.Namespace, expect.ConfigMapRef.Name))
if expect.ConfigMap != nil {
expectMessage = fmt.Sprintf(controller.EventMessageFmt, fmt.Sprintf("/api/v1/namespaces/%s/configmaps/%s", expect.ConfigMap.Namespace, expect.ConfigMap.Name))
}
}
if expectMessage != recent.Message {

View File

@ -57,8 +57,10 @@ var startServices = flag.Bool("start-services", true, "If true, start local node
var stopServices = flag.Bool("stop-services", true, "If true, stop local node services after running tests")
var busyboxImage = "busybox"
// Kubelet internal cgroup name for node allocatable cgroup.
const defaultNodeAllocatableCgroup = "kubepods"
const (
// Kubelet internal cgroup name for node allocatable cgroup.
defaultNodeAllocatableCgroup = "kubepods"
)
func getNodeSummary() (*stats.Summary, error) {
req, err := http.NewRequest("GET", *kubeletAddress+"/stats/summary", nil)
@ -167,10 +169,11 @@ func setKubeletConfiguration(f *framework.Framework, kubeCfg *kubeletconfig.Kube
// create the reference and set Node.Spec.ConfigSource
src := &apiv1.NodeConfigSource{
ConfigMapRef: &apiv1.ObjectReference{
Namespace: "kube-system",
Name: cm.Name,
UID: cm.UID,
ConfigMap: &apiv1.ConfigMapNodeConfigSource{
Namespace: "kube-system",
Name: cm.Name,
UID: cm.UID,
KubeletConfigKey: "kubelet",
},
}

View File

@ -54,7 +54,6 @@ var kindWhiteList = sets.NewString(
"ExportOptions",
"GetOptions",
"ListOptions",
"NodeConfigSource",
"NodeProxyOptions",
"PodAttachOptions",
"PodExecOptions",

View File

@ -286,12 +286,13 @@ func TestNodeAuthorizer(t *testing.T) {
return err
}
node2.Spec.ConfigSource = &api.NodeConfigSource{
ConfigMapRef: &api.ObjectReference{
ConfigMap: &api.ConfigMapNodeConfigSource{
Namespace: "ns",
Name: "myconfigmapconfigsource",
// validation just requires UID to be non-empty and it isn't necessary for GET,
// so we just use a bogus one for the test
UID: "uid",
UID: "uid",
KubeletConfigKey: "kubelet",
},
}
_, err = client.Core().Nodes().Update(node2)

View File

@ -437,7 +437,6 @@ var ephemeralWhiteList = createEphemeralWhiteList(
gvk("", "v1", "RangeAllocation"), // stored in various places in etcd but cannot be directly created
gvk("", "v1", "ComponentStatus"), // status info not stored in etcd
gvk("", "v1", "SerializedReference"), // used for serilization, not stored in etcd
gvk("", "v1", "NodeConfigSource"), // subfield of node.spec, but shouldn't be directly created
gvk("", "v1", "PodStatusResult"), // wrapper object not stored in etcd
// --