1/*
2Copyright 2019 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package testing
18
19import (
20	"fmt"
21
22	"k8s.io/api/core/v1"
23	"k8s.io/apimachinery/pkg/api/resource"
24	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25	"k8s.io/apimachinery/pkg/runtime/schema"
26	"k8s.io/apimachinery/pkg/types"
27	"k8s.io/utils/pointer"
28)
29
30var zero int64
31
32// NodeSelectorWrapper wraps a NodeSelector inside.
33type NodeSelectorWrapper struct{ v1.NodeSelector }
34
35// MakeNodeSelector creates a NodeSelector wrapper.
36func MakeNodeSelector() *NodeSelectorWrapper {
37	return &NodeSelectorWrapper{v1.NodeSelector{}}
38}
39
40// In injects a matchExpression (with an operator IN) as a selectorTerm
41// to the inner nodeSelector.
42// NOTE: appended selecterTerms are ORed.
43func (s *NodeSelectorWrapper) In(key string, vals []string) *NodeSelectorWrapper {
44	expression := v1.NodeSelectorRequirement{
45		Key:      key,
46		Operator: v1.NodeSelectorOpIn,
47		Values:   vals,
48	}
49	selectorTerm := v1.NodeSelectorTerm{}
50	selectorTerm.MatchExpressions = append(selectorTerm.MatchExpressions, expression)
51	s.NodeSelectorTerms = append(s.NodeSelectorTerms, selectorTerm)
52	return s
53}
54
55// NotIn injects a matchExpression (with an operator NotIn) as a selectorTerm
56// to the inner nodeSelector.
57func (s *NodeSelectorWrapper) NotIn(key string, vals []string) *NodeSelectorWrapper {
58	expression := v1.NodeSelectorRequirement{
59		Key:      key,
60		Operator: v1.NodeSelectorOpNotIn,
61		Values:   vals,
62	}
63	selectorTerm := v1.NodeSelectorTerm{}
64	selectorTerm.MatchExpressions = append(selectorTerm.MatchExpressions, expression)
65	s.NodeSelectorTerms = append(s.NodeSelectorTerms, selectorTerm)
66	return s
67}
68
69// Obj returns the inner NodeSelector.
70func (s *NodeSelectorWrapper) Obj() *v1.NodeSelector {
71	return &s.NodeSelector
72}
73
74// LabelSelectorWrapper wraps a LabelSelector inside.
75type LabelSelectorWrapper struct{ metav1.LabelSelector }
76
77// MakeLabelSelector creates a LabelSelector wrapper.
78func MakeLabelSelector() *LabelSelectorWrapper {
79	return &LabelSelectorWrapper{metav1.LabelSelector{}}
80}
81
82// Label applies a {k,v} pair to the inner LabelSelector.
83func (s *LabelSelectorWrapper) Label(k, v string) *LabelSelectorWrapper {
84	if s.MatchLabels == nil {
85		s.MatchLabels = make(map[string]string)
86	}
87	s.MatchLabels[k] = v
88	return s
89}
90
91// In injects a matchExpression (with an operator In) to the inner labelSelector.
92func (s *LabelSelectorWrapper) In(key string, vals []string) *LabelSelectorWrapper {
93	expression := metav1.LabelSelectorRequirement{
94		Key:      key,
95		Operator: metav1.LabelSelectorOpIn,
96		Values:   vals,
97	}
98	s.MatchExpressions = append(s.MatchExpressions, expression)
99	return s
100}
101
102// NotIn injects a matchExpression (with an operator NotIn) to the inner labelSelector.
103func (s *LabelSelectorWrapper) NotIn(key string, vals []string) *LabelSelectorWrapper {
104	expression := metav1.LabelSelectorRequirement{
105		Key:      key,
106		Operator: metav1.LabelSelectorOpNotIn,
107		Values:   vals,
108	}
109	s.MatchExpressions = append(s.MatchExpressions, expression)
110	return s
111}
112
113// Exists injects a matchExpression (with an operator Exists) to the inner labelSelector.
114func (s *LabelSelectorWrapper) Exists(k string) *LabelSelectorWrapper {
115	expression := metav1.LabelSelectorRequirement{
116		Key:      k,
117		Operator: metav1.LabelSelectorOpExists,
118	}
119	s.MatchExpressions = append(s.MatchExpressions, expression)
120	return s
121}
122
123// NotExist injects a matchExpression (with an operator NotExist) to the inner labelSelector.
124func (s *LabelSelectorWrapper) NotExist(k string) *LabelSelectorWrapper {
125	expression := metav1.LabelSelectorRequirement{
126		Key:      k,
127		Operator: metav1.LabelSelectorOpDoesNotExist,
128	}
129	s.MatchExpressions = append(s.MatchExpressions, expression)
130	return s
131}
132
133// Obj returns the inner LabelSelector.
134func (s *LabelSelectorWrapper) Obj() *metav1.LabelSelector {
135	return &s.LabelSelector
136}
137
138// PodWrapper wraps a Pod inside.
139type PodWrapper struct{ v1.Pod }
140
141// MakePod creates a Pod wrapper.
142func MakePod() *PodWrapper {
143	return &PodWrapper{v1.Pod{}}
144}
145
146// Obj returns the inner Pod.
147func (p *PodWrapper) Obj() *v1.Pod {
148	return &p.Pod
149}
150
151// Name sets `s` as the name of the inner pod.
152func (p *PodWrapper) Name(s string) *PodWrapper {
153	p.SetName(s)
154	return p
155}
156
157// UID sets `s` as the UID of the inner pod.
158func (p *PodWrapper) UID(s string) *PodWrapper {
159	p.SetUID(types.UID(s))
160	return p
161}
162
163// SchedulerName sets `s` as the scheduler name of the inner pod.
164func (p *PodWrapper) SchedulerName(s string) *PodWrapper {
165	p.Spec.SchedulerName = s
166	return p
167}
168
169// Namespace sets `s` as the namespace of the inner pod.
170func (p *PodWrapper) Namespace(s string) *PodWrapper {
171	p.SetNamespace(s)
172	return p
173}
174
175// OwnerReference updates the owning controller of the pod.
176func (p *PodWrapper) OwnerReference(name string, gvk schema.GroupVersionKind) *PodWrapper {
177	p.OwnerReferences = []metav1.OwnerReference{
178		{
179			APIVersion: gvk.GroupVersion().String(),
180			Kind:       gvk.Kind,
181			Name:       name,
182			Controller: pointer.BoolPtr(true),
183		},
184	}
185	return p
186}
187
188// Container appends a container into PodSpec of the inner pod.
189func (p *PodWrapper) Container(s string) *PodWrapper {
190	p.Spec.Containers = append(p.Spec.Containers, v1.Container{
191		Name:  fmt.Sprintf("con%d", len(p.Spec.Containers)),
192		Image: s,
193	})
194	return p
195}
196
197// Priority sets a priority value into PodSpec of the inner pod.
198func (p *PodWrapper) Priority(val int32) *PodWrapper {
199	p.Spec.Priority = &val
200	return p
201}
202
203// Terminating sets the inner pod's deletionTimestamp to current timestamp.
204func (p *PodWrapper) Terminating() *PodWrapper {
205	now := metav1.Now()
206	p.DeletionTimestamp = &now
207	return p
208}
209
210// ZeroTerminationGracePeriod sets the TerminationGracePeriodSeconds of the inner pod to zero.
211func (p *PodWrapper) ZeroTerminationGracePeriod() *PodWrapper {
212	p.Spec.TerminationGracePeriodSeconds = &zero
213	return p
214}
215
216// Node sets `s` as the nodeName of the inner pod.
217func (p *PodWrapper) Node(s string) *PodWrapper {
218	p.Spec.NodeName = s
219	return p
220}
221
222// NodeSelector sets `m` as the nodeSelector of the inner pod.
223func (p *PodWrapper) NodeSelector(m map[string]string) *PodWrapper {
224	p.Spec.NodeSelector = m
225	return p
226}
227
228// NodeAffinityIn creates a HARD node affinity (with the operator In)
229// and injects into the inner pod.
230func (p *PodWrapper) NodeAffinityIn(key string, vals []string) *PodWrapper {
231	if p.Spec.Affinity == nil {
232		p.Spec.Affinity = &v1.Affinity{}
233	}
234	if p.Spec.Affinity.NodeAffinity == nil {
235		p.Spec.Affinity.NodeAffinity = &v1.NodeAffinity{}
236	}
237	nodeSelector := MakeNodeSelector().In(key, vals).Obj()
238	p.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution = nodeSelector
239	return p
240}
241
242// NodeAffinityNotIn creates a HARD node affinity (with the operator NotIn)
243// and injects into the inner pod.
244func (p *PodWrapper) NodeAffinityNotIn(key string, vals []string) *PodWrapper {
245	if p.Spec.Affinity == nil {
246		p.Spec.Affinity = &v1.Affinity{}
247	}
248	if p.Spec.Affinity.NodeAffinity == nil {
249		p.Spec.Affinity.NodeAffinity = &v1.NodeAffinity{}
250	}
251	nodeSelector := MakeNodeSelector().NotIn(key, vals).Obj()
252	p.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution = nodeSelector
253	return p
254}
255
256// StartTime sets `t` as .status.startTime for the inner pod.
257func (p *PodWrapper) StartTime(t metav1.Time) *PodWrapper {
258	p.Status.StartTime = &t
259	return p
260}
261
262// NominatedNodeName sets `n` as the .Status.NominatedNodeName of the inner pod.
263func (p *PodWrapper) NominatedNodeName(n string) *PodWrapper {
264	p.Status.NominatedNodeName = n
265	return p
266}
267
268// Toleration creates a toleration (with the operator Exists)
269// and injects into the inner pod.
270func (p *PodWrapper) Toleration(key string) *PodWrapper {
271	p.Spec.Tolerations = append(p.Spec.Tolerations, v1.Toleration{
272		Key:      key,
273		Operator: v1.TolerationOpExists,
274	})
275	return p
276}
277
278// HostPort creates a container with a hostPort valued `hostPort`,
279// and injects into the inner pod.
280func (p *PodWrapper) HostPort(port int32) *PodWrapper {
281	p.Spec.Containers = append(p.Spec.Containers, v1.Container{
282		Ports: []v1.ContainerPort{{HostPort: port}},
283	})
284	return p
285}
286
287// PodAffinityKind represents different kinds of PodAffinity.
288type PodAffinityKind int
289
290const (
291	// NilPodAffinity is a no-op which doesn't apply any PodAffinity.
292	NilPodAffinity PodAffinityKind = iota
293	// PodAffinityWithRequiredReq applies a HARD requirement to pod.spec.affinity.PodAffinity.
294	PodAffinityWithRequiredReq
295	// PodAffinityWithPreferredReq applies a SOFT requirement to pod.spec.affinity.PodAffinity.
296	PodAffinityWithPreferredReq
297	// PodAffinityWithRequiredPreferredReq applies HARD and SOFT requirements to pod.spec.affinity.PodAffinity.
298	PodAffinityWithRequiredPreferredReq
299	// PodAntiAffinityWithRequiredReq applies a HARD requirement to pod.spec.affinity.PodAntiAffinity.
300	PodAntiAffinityWithRequiredReq
301	// PodAntiAffinityWithPreferredReq applies a SOFT requirement to pod.spec.affinity.PodAntiAffinity.
302	PodAntiAffinityWithPreferredReq
303	// PodAntiAffinityWithRequiredPreferredReq applies HARD and SOFT requirements to pod.spec.affinity.PodAntiAffinity.
304	PodAntiAffinityWithRequiredPreferredReq
305)
306
307// PodAffinityExists creates an PodAffinity with the operator "Exists"
308// and injects into the inner pod.
309func (p *PodWrapper) PodAffinityExists(labelKey, topologyKey string, kind PodAffinityKind) *PodWrapper {
310	if kind == NilPodAffinity {
311		return p
312	}
313
314	if p.Spec.Affinity == nil {
315		p.Spec.Affinity = &v1.Affinity{}
316	}
317	if p.Spec.Affinity.PodAffinity == nil {
318		p.Spec.Affinity.PodAffinity = &v1.PodAffinity{}
319	}
320	labelSelector := MakeLabelSelector().Exists(labelKey).Obj()
321	term := v1.PodAffinityTerm{LabelSelector: labelSelector, TopologyKey: topologyKey}
322	switch kind {
323	case PodAffinityWithRequiredReq:
324		p.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution = append(
325			p.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution,
326			term,
327		)
328	case PodAffinityWithPreferredReq:
329		p.Spec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution = append(
330			p.Spec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution,
331			v1.WeightedPodAffinityTerm{Weight: 1, PodAffinityTerm: term},
332		)
333	case PodAffinityWithRequiredPreferredReq:
334		p.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution = append(
335			p.Spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution,
336			term,
337		)
338		p.Spec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution = append(
339			p.Spec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution,
340			v1.WeightedPodAffinityTerm{Weight: 1, PodAffinityTerm: term},
341		)
342	}
343	return p
344}
345
346// PodAntiAffinityExists creates an PodAntiAffinity with the operator "Exists"
347// and injects into the inner pod.
348func (p *PodWrapper) PodAntiAffinityExists(labelKey, topologyKey string, kind PodAffinityKind) *PodWrapper {
349	if kind == NilPodAffinity {
350		return p
351	}
352
353	if p.Spec.Affinity == nil {
354		p.Spec.Affinity = &v1.Affinity{}
355	}
356	if p.Spec.Affinity.PodAntiAffinity == nil {
357		p.Spec.Affinity.PodAntiAffinity = &v1.PodAntiAffinity{}
358	}
359	labelSelector := MakeLabelSelector().Exists(labelKey).Obj()
360	term := v1.PodAffinityTerm{LabelSelector: labelSelector, TopologyKey: topologyKey}
361	switch kind {
362	case PodAntiAffinityWithRequiredReq:
363		p.Spec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution = append(
364			p.Spec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution,
365			term,
366		)
367	case PodAntiAffinityWithPreferredReq:
368		p.Spec.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution = append(
369			p.Spec.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution,
370			v1.WeightedPodAffinityTerm{Weight: 1, PodAffinityTerm: term},
371		)
372	case PodAntiAffinityWithRequiredPreferredReq:
373		p.Spec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution = append(
374			p.Spec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution,
375			term,
376		)
377		p.Spec.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution = append(
378			p.Spec.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution,
379			v1.WeightedPodAffinityTerm{Weight: 1, PodAffinityTerm: term},
380		)
381	}
382	return p
383}
384
385// SpreadConstraint constructs a TopologySpreadConstraint object and injects
386// into the inner pod.
387func (p *PodWrapper) SpreadConstraint(maxSkew int, tpKey string, mode v1.UnsatisfiableConstraintAction, selector *metav1.LabelSelector) *PodWrapper {
388	c := v1.TopologySpreadConstraint{
389		MaxSkew:           int32(maxSkew),
390		TopologyKey:       tpKey,
391		WhenUnsatisfiable: mode,
392		LabelSelector:     selector,
393	}
394	p.Spec.TopologySpreadConstraints = append(p.Spec.TopologySpreadConstraints, c)
395	return p
396}
397
398// Label sets a {k,v} pair to the inner pod.
399func (p *PodWrapper) Label(k, v string) *PodWrapper {
400	if p.Labels == nil {
401		p.Labels = make(map[string]string)
402	}
403	p.Labels[k] = v
404	return p
405}
406
407// Req adds a new container to the inner pod with given resource map.
408func (p *PodWrapper) Req(resMap map[v1.ResourceName]string) *PodWrapper {
409	if len(resMap) == 0 {
410		return p
411	}
412
413	res := v1.ResourceList{}
414	for k, v := range resMap {
415		res[k] = resource.MustParse(v)
416	}
417	p.Spec.Containers = append(p.Spec.Containers, v1.Container{
418		Resources: v1.ResourceRequirements{
419			Requests: res,
420		},
421	})
422	return p
423}
424
425// PreemptionPolicy sets the give preemption policy to the inner pod.
426func (p *PodWrapper) PreemptionPolicy(policy v1.PreemptionPolicy) *PodWrapper {
427	p.Spec.PreemptionPolicy = &policy
428	return p
429}
430
431// NodeWrapper wraps a Node inside.
432type NodeWrapper struct{ v1.Node }
433
434// MakeNode creates a Node wrapper.
435func MakeNode() *NodeWrapper {
436	w := &NodeWrapper{v1.Node{}}
437	return w.Capacity(nil)
438}
439
440// Obj returns the inner Node.
441func (n *NodeWrapper) Obj() *v1.Node {
442	return &n.Node
443}
444
445// Name sets `s` as the name of the inner pod.
446func (n *NodeWrapper) Name(s string) *NodeWrapper {
447	n.SetName(s)
448	return n
449}
450
451// UID sets `s` as the UID of the inner pod.
452func (n *NodeWrapper) UID(s string) *NodeWrapper {
453	n.SetUID(types.UID(s))
454	return n
455}
456
457// Label applies a {k,v} label pair to the inner node.
458func (n *NodeWrapper) Label(k, v string) *NodeWrapper {
459	if n.Labels == nil {
460		n.Labels = make(map[string]string)
461	}
462	n.Labels[k] = v
463	return n
464}
465
466// Capacity sets the capacity and the allocatable resources of the inner node.
467// Each entry in `resources` corresponds to a resource name and its quantity.
468// By default, the capacity and allocatable number of pods are set to 32.
469func (n *NodeWrapper) Capacity(resources map[v1.ResourceName]string) *NodeWrapper {
470	res := v1.ResourceList{
471		v1.ResourcePods: resource.MustParse("32"),
472	}
473	for name, value := range resources {
474		res[name] = resource.MustParse(value)
475	}
476	n.Status.Capacity, n.Status.Allocatable = res, res
477	return n
478}
479
480// Images sets the images of the inner node. Each entry in `images` corresponds
481// to an image name and its size in bytes.
482func (n *NodeWrapper) Images(images map[string]int64) *NodeWrapper {
483	var containerImages []v1.ContainerImage
484	for name, size := range images {
485		containerImages = append(containerImages, v1.ContainerImage{Names: []string{name}, SizeBytes: size})
486	}
487	n.Status.Images = containerImages
488	return n
489}
490