1
16
17 package node
18
19 import (
20 "context"
21 "fmt"
22 "net"
23 "net/http"
24 "net/url"
25
26 "k8s.io/apimachinery/pkg/api/errors"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/fields"
29 "k8s.io/apimachinery/pkg/labels"
30 "k8s.io/apimachinery/pkg/runtime"
31 "k8s.io/apimachinery/pkg/types"
32 utilnet "k8s.io/apimachinery/pkg/util/net"
33 "k8s.io/apimachinery/pkg/util/validation/field"
34 "k8s.io/apiserver/pkg/registry/generic"
35 pkgstorage "k8s.io/apiserver/pkg/storage"
36 "k8s.io/apiserver/pkg/storage/names"
37 utilfeature "k8s.io/apiserver/pkg/util/feature"
38 "k8s.io/kubernetes/pkg/api/legacyscheme"
39 api "k8s.io/kubernetes/pkg/apis/core"
40 "k8s.io/kubernetes/pkg/apis/core/validation"
41 "k8s.io/kubernetes/pkg/features"
42 "k8s.io/kubernetes/pkg/kubelet/client"
43 "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
44 )
45
46
47 type nodeStrategy struct {
48 runtime.ObjectTyper
49 names.NameGenerator
50 }
51
52
53
54 var Strategy = nodeStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
55
56
57 func (nodeStrategy) NamespaceScoped() bool {
58 return false
59 }
60
61
62
63 func (nodeStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
64 fields := map[fieldpath.APIVersion]*fieldpath.Set{
65 "v1": fieldpath.NewSet(
66 fieldpath.MakePathOrDie("status"),
67 ),
68 }
69
70 return fields
71 }
72
73
74 func (nodeStrategy) AllowCreateOnUpdate() bool {
75 return false
76 }
77
78
79 func (nodeStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
80 node := obj.(*api.Node)
81 dropDisabledFields(node, nil)
82 }
83
84
85 func (nodeStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
86 newNode := obj.(*api.Node)
87 oldNode := old.(*api.Node)
88 newNode.Status = oldNode.Status
89
90 dropDisabledFields(newNode, oldNode)
91 }
92
93 func dropDisabledFields(node *api.Node, oldNode *api.Node) {
94
95
96 if oldNode == nil {
97 node.Spec.ConfigSource = nil
98 node.Status.Config = nil
99 }
100
101
102 if !nodeConfigSourceInUse(oldNode) && oldNode != nil {
103 node.Spec.ConfigSource = nil
104 }
105
106 if !utilfeature.DefaultFeatureGate.Enabled(features.RecursiveReadOnlyMounts) {
107 node.Status.RuntimeHandlers = nil
108 }
109 }
110
111
112 func nodeConfigSourceInUse(node *api.Node) bool {
113 if node == nil {
114 return false
115 }
116 if node.Spec.ConfigSource != nil {
117 return true
118 }
119 return false
120 }
121
122
123 func (nodeStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
124 node := obj.(*api.Node)
125 return validation.ValidateNode(node)
126 }
127
128
129 func (nodeStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
130 return fieldIsDeprecatedWarnings(obj)
131 }
132
133
134 func (nodeStrategy) Canonicalize(obj runtime.Object) {
135 }
136
137
138 func (nodeStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
139 errorList := validation.ValidateNode(obj.(*api.Node))
140 return append(errorList, validation.ValidateNodeUpdate(obj.(*api.Node), old.(*api.Node))...)
141 }
142
143
144 func (nodeStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
145 return fieldIsDeprecatedWarnings(obj)
146 }
147
148 func (nodeStrategy) AllowUnconditionalUpdate() bool {
149 return true
150 }
151
152 type nodeStatusStrategy struct {
153 nodeStrategy
154 }
155
156 var StatusStrategy = nodeStatusStrategy{Strategy}
157
158
159
160 func (nodeStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
161 fields := map[fieldpath.APIVersion]*fieldpath.Set{
162 "v1": fieldpath.NewSet(
163 fieldpath.MakePathOrDie("spec"),
164 ),
165 }
166
167 return fields
168 }
169
170 func (nodeStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
171 newNode := obj.(*api.Node)
172 oldNode := old.(*api.Node)
173 newNode.Spec = oldNode.Spec
174
175 if !nodeStatusConfigInUse(oldNode) {
176 newNode.Status.Config = nil
177 }
178 }
179
180
181 func nodeStatusConfigInUse(node *api.Node) bool {
182 if node == nil {
183 return false
184 }
185 if node.Status.Config != nil {
186 return true
187 }
188 return false
189 }
190
191 func (nodeStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
192 return validation.ValidateNodeUpdate(obj.(*api.Node), old.(*api.Node))
193 }
194
195
196 func (nodeStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
197 return nil
198 }
199
200
201 func (nodeStatusStrategy) Canonicalize(obj runtime.Object) {
202 }
203
204
205 type ResourceGetter interface {
206 Get(context.Context, string, *metav1.GetOptions) (runtime.Object, error)
207 }
208
209
210 func NodeToSelectableFields(node *api.Node) fields.Set {
211 objectMetaFieldsSet := generic.ObjectMetaFieldsSet(&node.ObjectMeta, false)
212 specificFieldsSet := fields.Set{
213 "spec.unschedulable": fmt.Sprint(node.Spec.Unschedulable),
214 }
215 return generic.MergeFieldsSets(objectMetaFieldsSet, specificFieldsSet)
216 }
217
218
219 func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
220 nodeObj, ok := obj.(*api.Node)
221 if !ok {
222 return nil, nil, fmt.Errorf("not a node")
223 }
224 return labels.Set(nodeObj.ObjectMeta.Labels), NodeToSelectableFields(nodeObj), nil
225 }
226
227
228 func MatchNode(label labels.Selector, field fields.Selector) pkgstorage.SelectionPredicate {
229 return pkgstorage.SelectionPredicate{
230 Label: label,
231 Field: field,
232 GetAttrs: GetAttrs,
233 }
234 }
235
236
237 func ResourceLocation(getter ResourceGetter, connection client.ConnectionInfoGetter, proxyTransport http.RoundTripper, ctx context.Context, id string) (*url.URL, http.RoundTripper, error) {
238 schemeReq, name, portReq, valid := utilnet.SplitSchemeNamePort(id)
239 if !valid {
240 return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid node request %q", id))
241 }
242
243 info, err := connection.GetConnectionInfo(ctx, types.NodeName(name))
244 if err != nil {
245 return nil, nil, err
246 }
247
248 if err := isProxyableHostname(ctx, info.Hostname); err != nil {
249 return nil, nil, errors.NewBadRequest(err.Error())
250 }
251
252
253
254
255 if portReq == "" || portReq == info.Port {
256 return &url.URL{
257 Scheme: info.Scheme,
258 Host: net.JoinHostPort(info.Hostname, info.Port),
259 },
260 info.Transport,
261 nil
262 }
263
264
265 return &url.URL{Scheme: schemeReq, Host: net.JoinHostPort(info.Hostname, portReq)}, proxyTransport, nil
266 }
267
268 func isProxyableHostname(ctx context.Context, hostname string) error {
269 resp, err := net.DefaultResolver.LookupIPAddr(ctx, hostname)
270 if err != nil {
271 return err
272 }
273
274 if len(resp) == 0 {
275 return fmt.Errorf("no addresses for hostname")
276 }
277 for _, host := range resp {
278 if !host.IP.IsGlobalUnicast() {
279 return fmt.Errorf("address not allowed")
280 }
281 }
282
283 return nil
284 }
285
286 func fieldIsDeprecatedWarnings(obj runtime.Object) []string {
287 newNode := obj.(*api.Node)
288 var warnings []string
289 if newNode.Spec.ConfigSource != nil {
290
291 warnings = append(warnings, "spec.configSource: the feature is removed")
292 }
293 if len(newNode.Spec.DoNotUseExternalID) > 0 {
294 warnings = append(warnings, "spec.externalID: this field is deprecated, and is unused by Kubernetes")
295 }
296 return warnings
297 }
298
View as plain text