mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-12-23 21:40:20 +00:00
1404 lines
41 KiB
Go
1404 lines
41 KiB
Go
|
/*
|
||
|
Copyright 2016 The Kubernetes Authors.
|
||
|
|
||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
you may not use this file except in compliance with the License.
|
||
|
You may obtain a copy of the License at
|
||
|
|
||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
||
|
Unless required by applicable law or agreed to in writing, software
|
||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
See the License for the specific language governing permissions and
|
||
|
limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package utils
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"math"
|
||
|
"os"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
apps "k8s.io/api/apps/v1"
|
||
|
batch "k8s.io/api/batch/v1"
|
||
|
v1 "k8s.io/api/core/v1"
|
||
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||
|
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||
|
"k8s.io/apimachinery/pkg/api/resource"
|
||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||
|
"k8s.io/apimachinery/pkg/fields"
|
||
|
"k8s.io/apimachinery/pkg/labels"
|
||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||
|
"k8s.io/apimachinery/pkg/types"
|
||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||
|
"k8s.io/apimachinery/pkg/util/uuid"
|
||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||
|
clientset "k8s.io/client-go/kubernetes"
|
||
|
scaleclient "k8s.io/client-go/scale"
|
||
|
"k8s.io/client-go/util/workqueue"
|
||
|
batchinternal "k8s.io/kubernetes/pkg/apis/batch"
|
||
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||
|
extensionsinternal "k8s.io/kubernetes/pkg/apis/extensions"
|
||
|
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||
|
|
||
|
"k8s.io/klog"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
// String used to mark pod deletion
|
||
|
nonExist = "NonExist"
|
||
|
)
|
||
|
|
||
|
func removePtr(replicas *int32) int32 {
|
||
|
if replicas == nil {
|
||
|
return 0
|
||
|
}
|
||
|
return *replicas
|
||
|
}
|
||
|
|
||
|
func WaitUntilPodIsScheduled(c clientset.Interface, name, namespace string, timeout time.Duration) (*v1.Pod, error) {
|
||
|
// Wait until it's scheduled
|
||
|
p, err := c.CoreV1().Pods(namespace).Get(name, metav1.GetOptions{ResourceVersion: "0"})
|
||
|
if err == nil && p.Spec.NodeName != "" {
|
||
|
return p, nil
|
||
|
}
|
||
|
pollingPeriod := 200 * time.Millisecond
|
||
|
startTime := time.Now()
|
||
|
for startTime.Add(timeout).After(time.Now()) {
|
||
|
time.Sleep(pollingPeriod)
|
||
|
p, err := c.CoreV1().Pods(namespace).Get(name, metav1.GetOptions{ResourceVersion: "0"})
|
||
|
if err == nil && p.Spec.NodeName != "" {
|
||
|
return p, nil
|
||
|
}
|
||
|
}
|
||
|
return nil, fmt.Errorf("Timed out after %v when waiting for pod %v/%v to start.", timeout, namespace, name)
|
||
|
}
|
||
|
|
||
|
func RunPodAndGetNodeName(c clientset.Interface, pod *v1.Pod, timeout time.Duration) (string, error) {
|
||
|
name := pod.Name
|
||
|
namespace := pod.Namespace
|
||
|
if err := CreatePodWithRetries(c, namespace, pod); err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
p, err := WaitUntilPodIsScheduled(c, name, namespace, timeout)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
return p.Spec.NodeName, nil
|
||
|
}
|
||
|
|
||
|
type RunObjectConfig interface {
|
||
|
Run() error
|
||
|
GetName() string
|
||
|
GetNamespace() string
|
||
|
GetKind() schema.GroupKind
|
||
|
GetClient() clientset.Interface
|
||
|
GetInternalClient() internalclientset.Interface
|
||
|
GetScalesGetter() scaleclient.ScalesGetter
|
||
|
SetClient(clientset.Interface)
|
||
|
SetInternalClient(internalclientset.Interface)
|
||
|
SetScalesClient(scaleclient.ScalesGetter)
|
||
|
GetReplicas() int
|
||
|
GetLabelValue(string) (string, bool)
|
||
|
GetGroupResource() schema.GroupResource
|
||
|
}
|
||
|
|
||
|
type RCConfig struct {
|
||
|
Affinity *v1.Affinity
|
||
|
Client clientset.Interface
|
||
|
InternalClient internalclientset.Interface
|
||
|
ScalesGetter scaleclient.ScalesGetter
|
||
|
Image string
|
||
|
Command []string
|
||
|
Name string
|
||
|
Namespace string
|
||
|
PollInterval time.Duration
|
||
|
Timeout time.Duration
|
||
|
PodStatusFile *os.File
|
||
|
Replicas int
|
||
|
CpuRequest int64 // millicores
|
||
|
CpuLimit int64 // millicores
|
||
|
MemRequest int64 // bytes
|
||
|
MemLimit int64 // bytes
|
||
|
GpuLimit int64 // count
|
||
|
ReadinessProbe *v1.Probe
|
||
|
DNSPolicy *v1.DNSPolicy
|
||
|
PriorityClassName string
|
||
|
|
||
|
// Env vars, set the same for every pod.
|
||
|
Env map[string]string
|
||
|
|
||
|
// Extra labels and annotations added to every pod.
|
||
|
Labels map[string]string
|
||
|
Annotations map[string]string
|
||
|
|
||
|
// Node selector for pods in the RC.
|
||
|
NodeSelector map[string]string
|
||
|
|
||
|
// Tolerations for pods in the RC.
|
||
|
Tolerations []v1.Toleration
|
||
|
|
||
|
// Ports to declare in the container (map of name to containerPort).
|
||
|
Ports map[string]int
|
||
|
// Ports to declare in the container as host and container ports.
|
||
|
HostPorts map[string]int
|
||
|
|
||
|
Volumes []v1.Volume
|
||
|
VolumeMounts []v1.VolumeMount
|
||
|
|
||
|
// Pointer to a list of pods; if non-nil, will be set to a list of pods
|
||
|
// created by this RC by RunRC.
|
||
|
CreatedPods *[]*v1.Pod
|
||
|
|
||
|
// Maximum allowable container failures. If exceeded, RunRC returns an error.
|
||
|
// Defaults to replicas*0.1 if unspecified.
|
||
|
MaxContainerFailures *int
|
||
|
|
||
|
// If set to false starting RC will print progress, otherwise only errors will be printed.
|
||
|
Silent bool
|
||
|
|
||
|
// If set this function will be used to print log lines instead of klog.
|
||
|
LogFunc func(fmt string, args ...interface{})
|
||
|
// If set those functions will be used to gather data from Nodes - in integration tests where no
|
||
|
// kubelets are running those variables should be nil.
|
||
|
NodeDumpFunc func(c clientset.Interface, nodeNames []string, logFunc func(fmt string, args ...interface{}))
|
||
|
ContainerDumpFunc func(c clientset.Interface, ns string, logFunc func(ftm string, args ...interface{}))
|
||
|
|
||
|
// Names of the secrets and configmaps to mount.
|
||
|
SecretNames []string
|
||
|
ConfigMapNames []string
|
||
|
|
||
|
ServiceAccountTokenProjections int
|
||
|
}
|
||
|
|
||
|
func (rc *RCConfig) RCConfigLog(fmt string, args ...interface{}) {
|
||
|
if rc.LogFunc != nil {
|
||
|
rc.LogFunc(fmt, args...)
|
||
|
}
|
||
|
klog.Infof(fmt, args...)
|
||
|
}
|
||
|
|
||
|
type DeploymentConfig struct {
|
||
|
RCConfig
|
||
|
}
|
||
|
|
||
|
type ReplicaSetConfig struct {
|
||
|
RCConfig
|
||
|
}
|
||
|
|
||
|
type JobConfig struct {
|
||
|
RCConfig
|
||
|
}
|
||
|
|
||
|
// podInfo contains pod information useful for debugging e2e tests.
|
||
|
type podInfo struct {
|
||
|
oldHostname string
|
||
|
oldPhase string
|
||
|
hostname string
|
||
|
phase string
|
||
|
}
|
||
|
|
||
|
// PodDiff is a map of pod name to podInfos
|
||
|
type PodDiff map[string]*podInfo
|
||
|
|
||
|
// Print formats and prints the give PodDiff.
|
||
|
func (p PodDiff) String(ignorePhases sets.String) string {
|
||
|
ret := ""
|
||
|
for name, info := range p {
|
||
|
if ignorePhases.Has(info.phase) {
|
||
|
continue
|
||
|
}
|
||
|
if info.phase == nonExist {
|
||
|
ret += fmt.Sprintf("Pod %v was deleted, had phase %v and host %v\n", name, info.oldPhase, info.oldHostname)
|
||
|
continue
|
||
|
}
|
||
|
phaseChange, hostChange := false, false
|
||
|
msg := fmt.Sprintf("Pod %v ", name)
|
||
|
if info.oldPhase != info.phase {
|
||
|
phaseChange = true
|
||
|
if info.oldPhase == nonExist {
|
||
|
msg += fmt.Sprintf("in phase %v ", info.phase)
|
||
|
} else {
|
||
|
msg += fmt.Sprintf("went from phase: %v -> %v ", info.oldPhase, info.phase)
|
||
|
}
|
||
|
}
|
||
|
if info.oldHostname != info.hostname {
|
||
|
hostChange = true
|
||
|
if info.oldHostname == nonExist || info.oldHostname == "" {
|
||
|
msg += fmt.Sprintf("assigned host %v ", info.hostname)
|
||
|
} else {
|
||
|
msg += fmt.Sprintf("went from host: %v -> %v ", info.oldHostname, info.hostname)
|
||
|
}
|
||
|
}
|
||
|
if phaseChange || hostChange {
|
||
|
ret += msg + "\n"
|
||
|
}
|
||
|
}
|
||
|
return ret
|
||
|
}
|
||
|
|
||
|
// DeletedPods returns a slice of pods that were present at the beginning
|
||
|
// and then disappeared.
|
||
|
func (p PodDiff) DeletedPods() []string {
|
||
|
var deletedPods []string
|
||
|
for podName, podInfo := range p {
|
||
|
if podInfo.hostname == nonExist {
|
||
|
deletedPods = append(deletedPods, podName)
|
||
|
}
|
||
|
}
|
||
|
return deletedPods
|
||
|
}
|
||
|
|
||
|
// Diff computes a PodDiff given 2 lists of pods.
|
||
|
func Diff(oldPods []*v1.Pod, curPods []*v1.Pod) PodDiff {
|
||
|
podInfoMap := PodDiff{}
|
||
|
|
||
|
// New pods will show up in the curPods list but not in oldPods. They have oldhostname/phase == nonexist.
|
||
|
for _, pod := range curPods {
|
||
|
podInfoMap[pod.Name] = &podInfo{hostname: pod.Spec.NodeName, phase: string(pod.Status.Phase), oldHostname: nonExist, oldPhase: nonExist}
|
||
|
}
|
||
|
|
||
|
// Deleted pods will show up in the oldPods list but not in curPods. They have a hostname/phase == nonexist.
|
||
|
for _, pod := range oldPods {
|
||
|
if info, ok := podInfoMap[pod.Name]; ok {
|
||
|
info.oldHostname, info.oldPhase = pod.Spec.NodeName, string(pod.Status.Phase)
|
||
|
} else {
|
||
|
podInfoMap[pod.Name] = &podInfo{hostname: nonExist, phase: nonExist, oldHostname: pod.Spec.NodeName, oldPhase: string(pod.Status.Phase)}
|
||
|
}
|
||
|
}
|
||
|
return podInfoMap
|
||
|
}
|
||
|
|
||
|
// RunDeployment Launches (and verifies correctness) of a Deployment
|
||
|
// and will wait for all pods it spawns to become "Running".
|
||
|
// It's the caller's responsibility to clean up externally (i.e. use the
|
||
|
// namespace lifecycle for handling Cleanup).
|
||
|
func RunDeployment(config DeploymentConfig) error {
|
||
|
err := config.create()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return config.start()
|
||
|
}
|
||
|
|
||
|
func (config *DeploymentConfig) Run() error {
|
||
|
return RunDeployment(*config)
|
||
|
}
|
||
|
|
||
|
func (config *DeploymentConfig) GetKind() schema.GroupKind {
|
||
|
return extensionsinternal.Kind("Deployment")
|
||
|
}
|
||
|
|
||
|
func (config *DeploymentConfig) GetGroupResource() schema.GroupResource {
|
||
|
return extensionsinternal.Resource("deployments")
|
||
|
}
|
||
|
|
||
|
func (config *DeploymentConfig) create() error {
|
||
|
deployment := &apps.Deployment{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Name: config.Name,
|
||
|
},
|
||
|
Spec: apps.DeploymentSpec{
|
||
|
Replicas: func(i int) *int32 { x := int32(i); return &x }(config.Replicas),
|
||
|
Selector: &metav1.LabelSelector{
|
||
|
MatchLabels: map[string]string{
|
||
|
"name": config.Name,
|
||
|
},
|
||
|
},
|
||
|
Template: v1.PodTemplateSpec{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Labels: map[string]string{"name": config.Name},
|
||
|
Annotations: config.Annotations,
|
||
|
},
|
||
|
Spec: v1.PodSpec{
|
||
|
Affinity: config.Affinity,
|
||
|
Containers: []v1.Container{
|
||
|
{
|
||
|
Name: config.Name,
|
||
|
Image: config.Image,
|
||
|
Command: config.Command,
|
||
|
Ports: []v1.ContainerPort{{ContainerPort: 80}},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
if len(config.SecretNames) > 0 {
|
||
|
attachSecrets(&deployment.Spec.Template, config.SecretNames)
|
||
|
}
|
||
|
if len(config.ConfigMapNames) > 0 {
|
||
|
attachConfigMaps(&deployment.Spec.Template, config.ConfigMapNames)
|
||
|
}
|
||
|
|
||
|
for i := 0; i < config.ServiceAccountTokenProjections; i++ {
|
||
|
attachServiceAccountTokenProjection(&deployment.Spec.Template, fmt.Sprintf("tok-%d", i))
|
||
|
}
|
||
|
|
||
|
config.applyTo(&deployment.Spec.Template)
|
||
|
|
||
|
if err := CreateDeploymentWithRetries(config.Client, config.Namespace, deployment); err != nil {
|
||
|
return fmt.Errorf("Error creating deployment: %v", err)
|
||
|
}
|
||
|
config.RCConfigLog("Created deployment with name: %v, namespace: %v, replica count: %v", deployment.Name, config.Namespace, removePtr(deployment.Spec.Replicas))
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// RunReplicaSet launches (and verifies correctness) of a ReplicaSet
|
||
|
// and waits until all the pods it launches to reach the "Running" state.
|
||
|
// It's the caller's responsibility to clean up externally (i.e. use the
|
||
|
// namespace lifecycle for handling Cleanup).
|
||
|
func RunReplicaSet(config ReplicaSetConfig) error {
|
||
|
err := config.create()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return config.start()
|
||
|
}
|
||
|
|
||
|
func (config *ReplicaSetConfig) Run() error {
|
||
|
return RunReplicaSet(*config)
|
||
|
}
|
||
|
|
||
|
func (config *ReplicaSetConfig) GetKind() schema.GroupKind {
|
||
|
return extensionsinternal.Kind("ReplicaSet")
|
||
|
}
|
||
|
|
||
|
func (config *ReplicaSetConfig) GetGroupResource() schema.GroupResource {
|
||
|
return extensionsinternal.Resource("replicasets")
|
||
|
}
|
||
|
|
||
|
func (config *ReplicaSetConfig) create() error {
|
||
|
rs := &apps.ReplicaSet{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Name: config.Name,
|
||
|
},
|
||
|
Spec: apps.ReplicaSetSpec{
|
||
|
Replicas: func(i int) *int32 { x := int32(i); return &x }(config.Replicas),
|
||
|
Selector: &metav1.LabelSelector{
|
||
|
MatchLabels: map[string]string{
|
||
|
"name": config.Name,
|
||
|
},
|
||
|
},
|
||
|
Template: v1.PodTemplateSpec{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Labels: map[string]string{"name": config.Name},
|
||
|
Annotations: config.Annotations,
|
||
|
},
|
||
|
Spec: v1.PodSpec{
|
||
|
Affinity: config.Affinity,
|
||
|
Containers: []v1.Container{
|
||
|
{
|
||
|
Name: config.Name,
|
||
|
Image: config.Image,
|
||
|
Command: config.Command,
|
||
|
Ports: []v1.ContainerPort{{ContainerPort: 80}},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
if len(config.SecretNames) > 0 {
|
||
|
attachSecrets(&rs.Spec.Template, config.SecretNames)
|
||
|
}
|
||
|
if len(config.ConfigMapNames) > 0 {
|
||
|
attachConfigMaps(&rs.Spec.Template, config.ConfigMapNames)
|
||
|
}
|
||
|
|
||
|
config.applyTo(&rs.Spec.Template)
|
||
|
|
||
|
if err := CreateReplicaSetWithRetries(config.Client, config.Namespace, rs); err != nil {
|
||
|
return fmt.Errorf("Error creating replica set: %v", err)
|
||
|
}
|
||
|
config.RCConfigLog("Created replica set with name: %v, namespace: %v, replica count: %v", rs.Name, config.Namespace, removePtr(rs.Spec.Replicas))
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// RunJob baunches (and verifies correctness) of a Job
|
||
|
// and will wait for all pods it spawns to become "Running".
|
||
|
// It's the caller's responsibility to clean up externally (i.e. use the
|
||
|
// namespace lifecycle for handling Cleanup).
|
||
|
func RunJob(config JobConfig) error {
|
||
|
err := config.create()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return config.start()
|
||
|
}
|
||
|
|
||
|
func (config *JobConfig) Run() error {
|
||
|
return RunJob(*config)
|
||
|
}
|
||
|
|
||
|
func (config *JobConfig) GetKind() schema.GroupKind {
|
||
|
return batchinternal.Kind("Job")
|
||
|
}
|
||
|
|
||
|
func (config *JobConfig) GetGroupResource() schema.GroupResource {
|
||
|
return batchinternal.Resource("jobs")
|
||
|
}
|
||
|
|
||
|
func (config *JobConfig) create() error {
|
||
|
job := &batch.Job{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Name: config.Name,
|
||
|
},
|
||
|
Spec: batch.JobSpec{
|
||
|
Parallelism: func(i int) *int32 { x := int32(i); return &x }(config.Replicas),
|
||
|
Completions: func(i int) *int32 { x := int32(i); return &x }(config.Replicas),
|
||
|
Template: v1.PodTemplateSpec{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Labels: map[string]string{"name": config.Name},
|
||
|
Annotations: config.Annotations,
|
||
|
},
|
||
|
Spec: v1.PodSpec{
|
||
|
Affinity: config.Affinity,
|
||
|
Containers: []v1.Container{
|
||
|
{
|
||
|
Name: config.Name,
|
||
|
Image: config.Image,
|
||
|
Command: config.Command,
|
||
|
},
|
||
|
},
|
||
|
RestartPolicy: v1.RestartPolicyOnFailure,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
if len(config.SecretNames) > 0 {
|
||
|
attachSecrets(&job.Spec.Template, config.SecretNames)
|
||
|
}
|
||
|
if len(config.ConfigMapNames) > 0 {
|
||
|
attachConfigMaps(&job.Spec.Template, config.ConfigMapNames)
|
||
|
}
|
||
|
|
||
|
config.applyTo(&job.Spec.Template)
|
||
|
|
||
|
if err := CreateJobWithRetries(config.Client, config.Namespace, job); err != nil {
|
||
|
return fmt.Errorf("Error creating job: %v", err)
|
||
|
}
|
||
|
config.RCConfigLog("Created job with name: %v, namespace: %v, parallelism/completions: %v", job.Name, config.Namespace, job.Spec.Parallelism)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// RunRC Launches (and verifies correctness) of a Replication Controller
|
||
|
// and will wait for all pods it spawns to become "Running".
|
||
|
// It's the caller's responsibility to clean up externally (i.e. use the
|
||
|
// namespace lifecycle for handling Cleanup).
|
||
|
func RunRC(config RCConfig) error {
|
||
|
err := config.create()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return config.start()
|
||
|
}
|
||
|
|
||
|
func (config *RCConfig) Run() error {
|
||
|
return RunRC(*config)
|
||
|
}
|
||
|
|
||
|
func (config *RCConfig) GetName() string {
|
||
|
return config.Name
|
||
|
}
|
||
|
|
||
|
func (config *RCConfig) GetNamespace() string {
|
||
|
return config.Namespace
|
||
|
}
|
||
|
|
||
|
func (config *RCConfig) GetKind() schema.GroupKind {
|
||
|
return api.Kind("ReplicationController")
|
||
|
}
|
||
|
|
||
|
func (config *RCConfig) GetGroupResource() schema.GroupResource {
|
||
|
return api.Resource("replicationcontrollers")
|
||
|
}
|
||
|
|
||
|
func (config *RCConfig) GetClient() clientset.Interface {
|
||
|
return config.Client
|
||
|
}
|
||
|
|
||
|
func (config *RCConfig) GetInternalClient() internalclientset.Interface {
|
||
|
return config.InternalClient
|
||
|
}
|
||
|
|
||
|
func (config *RCConfig) GetScalesGetter() scaleclient.ScalesGetter {
|
||
|
return config.ScalesGetter
|
||
|
}
|
||
|
|
||
|
func (config *RCConfig) SetClient(c clientset.Interface) {
|
||
|
config.Client = c
|
||
|
}
|
||
|
|
||
|
func (config *RCConfig) SetInternalClient(c internalclientset.Interface) {
|
||
|
config.InternalClient = c
|
||
|
}
|
||
|
|
||
|
func (config *RCConfig) SetScalesClient(getter scaleclient.ScalesGetter) {
|
||
|
config.ScalesGetter = getter
|
||
|
}
|
||
|
|
||
|
func (config *RCConfig) GetReplicas() int {
|
||
|
return config.Replicas
|
||
|
}
|
||
|
|
||
|
func (config *RCConfig) GetLabelValue(key string) (string, bool) {
|
||
|
value, found := config.Labels[key]
|
||
|
return value, found
|
||
|
}
|
||
|
|
||
|
func (config *RCConfig) create() error {
|
||
|
dnsDefault := v1.DNSDefault
|
||
|
if config.DNSPolicy == nil {
|
||
|
config.DNSPolicy = &dnsDefault
|
||
|
}
|
||
|
one := int64(1)
|
||
|
rc := &v1.ReplicationController{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Name: config.Name,
|
||
|
},
|
||
|
Spec: v1.ReplicationControllerSpec{
|
||
|
Replicas: func(i int) *int32 { x := int32(i); return &x }(config.Replicas),
|
||
|
Selector: map[string]string{
|
||
|
"name": config.Name,
|
||
|
},
|
||
|
Template: &v1.PodTemplateSpec{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Labels: map[string]string{"name": config.Name},
|
||
|
Annotations: config.Annotations,
|
||
|
},
|
||
|
Spec: v1.PodSpec{
|
||
|
Affinity: config.Affinity,
|
||
|
Containers: []v1.Container{
|
||
|
{
|
||
|
Name: config.Name,
|
||
|
Image: config.Image,
|
||
|
Command: config.Command,
|
||
|
Ports: []v1.ContainerPort{{ContainerPort: 80}},
|
||
|
ReadinessProbe: config.ReadinessProbe,
|
||
|
},
|
||
|
},
|
||
|
DNSPolicy: *config.DNSPolicy,
|
||
|
NodeSelector: config.NodeSelector,
|
||
|
Tolerations: config.Tolerations,
|
||
|
TerminationGracePeriodSeconds: &one,
|
||
|
PriorityClassName: config.PriorityClassName,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
if len(config.SecretNames) > 0 {
|
||
|
attachSecrets(rc.Spec.Template, config.SecretNames)
|
||
|
}
|
||
|
if len(config.ConfigMapNames) > 0 {
|
||
|
attachConfigMaps(rc.Spec.Template, config.ConfigMapNames)
|
||
|
}
|
||
|
|
||
|
config.applyTo(rc.Spec.Template)
|
||
|
|
||
|
if err := CreateRCWithRetries(config.Client, config.Namespace, rc); err != nil {
|
||
|
return fmt.Errorf("Error creating replication controller: %v", err)
|
||
|
}
|
||
|
config.RCConfigLog("Created replication controller with name: %v, namespace: %v, replica count: %v", rc.Name, config.Namespace, removePtr(rc.Spec.Replicas))
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (config *RCConfig) applyTo(template *v1.PodTemplateSpec) {
|
||
|
if config.Env != nil {
|
||
|
for k, v := range config.Env {
|
||
|
c := &template.Spec.Containers[0]
|
||
|
c.Env = append(c.Env, v1.EnvVar{Name: k, Value: v})
|
||
|
}
|
||
|
}
|
||
|
if config.Labels != nil {
|
||
|
for k, v := range config.Labels {
|
||
|
template.ObjectMeta.Labels[k] = v
|
||
|
}
|
||
|
}
|
||
|
if config.NodeSelector != nil {
|
||
|
template.Spec.NodeSelector = make(map[string]string)
|
||
|
for k, v := range config.NodeSelector {
|
||
|
template.Spec.NodeSelector[k] = v
|
||
|
}
|
||
|
}
|
||
|
if config.Tolerations != nil {
|
||
|
template.Spec.Tolerations = append([]v1.Toleration{}, config.Tolerations...)
|
||
|
}
|
||
|
if config.Ports != nil {
|
||
|
for k, v := range config.Ports {
|
||
|
c := &template.Spec.Containers[0]
|
||
|
c.Ports = append(c.Ports, v1.ContainerPort{Name: k, ContainerPort: int32(v)})
|
||
|
}
|
||
|
}
|
||
|
if config.HostPorts != nil {
|
||
|
for k, v := range config.HostPorts {
|
||
|
c := &template.Spec.Containers[0]
|
||
|
c.Ports = append(c.Ports, v1.ContainerPort{Name: k, ContainerPort: int32(v), HostPort: int32(v)})
|
||
|
}
|
||
|
}
|
||
|
if config.CpuLimit > 0 || config.MemLimit > 0 || config.GpuLimit > 0 {
|
||
|
template.Spec.Containers[0].Resources.Limits = v1.ResourceList{}
|
||
|
}
|
||
|
if config.CpuLimit > 0 {
|
||
|
template.Spec.Containers[0].Resources.Limits[v1.ResourceCPU] = *resource.NewMilliQuantity(config.CpuLimit, resource.DecimalSI)
|
||
|
}
|
||
|
if config.MemLimit > 0 {
|
||
|
template.Spec.Containers[0].Resources.Limits[v1.ResourceMemory] = *resource.NewQuantity(config.MemLimit, resource.DecimalSI)
|
||
|
}
|
||
|
if config.CpuRequest > 0 || config.MemRequest > 0 {
|
||
|
template.Spec.Containers[0].Resources.Requests = v1.ResourceList{}
|
||
|
}
|
||
|
if config.CpuRequest > 0 {
|
||
|
template.Spec.Containers[0].Resources.Requests[v1.ResourceCPU] = *resource.NewMilliQuantity(config.CpuRequest, resource.DecimalSI)
|
||
|
}
|
||
|
if config.MemRequest > 0 {
|
||
|
template.Spec.Containers[0].Resources.Requests[v1.ResourceMemory] = *resource.NewQuantity(config.MemRequest, resource.DecimalSI)
|
||
|
}
|
||
|
if config.GpuLimit > 0 {
|
||
|
template.Spec.Containers[0].Resources.Limits["nvidia.com/gpu"] = *resource.NewQuantity(config.GpuLimit, resource.DecimalSI)
|
||
|
}
|
||
|
if len(config.Volumes) > 0 {
|
||
|
template.Spec.Volumes = config.Volumes
|
||
|
}
|
||
|
if len(config.VolumeMounts) > 0 {
|
||
|
template.Spec.Containers[0].VolumeMounts = config.VolumeMounts
|
||
|
}
|
||
|
if config.PriorityClassName != "" {
|
||
|
template.Spec.PriorityClassName = config.PriorityClassName
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type RCStartupStatus struct {
|
||
|
Expected int
|
||
|
Terminating int
|
||
|
Running int
|
||
|
RunningButNotReady int
|
||
|
Waiting int
|
||
|
Pending int
|
||
|
Scheduled int
|
||
|
Unknown int
|
||
|
Inactive int
|
||
|
FailedContainers int
|
||
|
Created []*v1.Pod
|
||
|
ContainerRestartNodes sets.String
|
||
|
}
|
||
|
|
||
|
func (s *RCStartupStatus) String(name string) string {
|
||
|
return fmt.Sprintf("%v Pods: %d out of %d created, %d running, %d pending, %d waiting, %d inactive, %d terminating, %d unknown, %d runningButNotReady ",
|
||
|
name, len(s.Created), s.Expected, s.Running, s.Pending, s.Waiting, s.Inactive, s.Terminating, s.Unknown, s.RunningButNotReady)
|
||
|
}
|
||
|
|
||
|
func ComputeRCStartupStatus(pods []*v1.Pod, expected int) RCStartupStatus {
|
||
|
startupStatus := RCStartupStatus{
|
||
|
Expected: expected,
|
||
|
Created: make([]*v1.Pod, 0, expected),
|
||
|
ContainerRestartNodes: sets.NewString(),
|
||
|
}
|
||
|
for _, p := range pods {
|
||
|
if p.DeletionTimestamp != nil {
|
||
|
startupStatus.Terminating++
|
||
|
continue
|
||
|
}
|
||
|
startupStatus.Created = append(startupStatus.Created, p)
|
||
|
if p.Status.Phase == v1.PodRunning {
|
||
|
ready := false
|
||
|
for _, c := range p.Status.Conditions {
|
||
|
if c.Type == v1.PodReady && c.Status == v1.ConditionTrue {
|
||
|
ready = true
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if ready {
|
||
|
// Only count a pod is running when it is also ready.
|
||
|
startupStatus.Running++
|
||
|
} else {
|
||
|
startupStatus.RunningButNotReady++
|
||
|
}
|
||
|
for _, v := range FailedContainers(p) {
|
||
|
startupStatus.FailedContainers = startupStatus.FailedContainers + v.Restarts
|
||
|
startupStatus.ContainerRestartNodes.Insert(p.Spec.NodeName)
|
||
|
}
|
||
|
} else if p.Status.Phase == v1.PodPending {
|
||
|
if p.Spec.NodeName == "" {
|
||
|
startupStatus.Waiting++
|
||
|
} else {
|
||
|
startupStatus.Pending++
|
||
|
}
|
||
|
} else if p.Status.Phase == v1.PodSucceeded || p.Status.Phase == v1.PodFailed {
|
||
|
startupStatus.Inactive++
|
||
|
} else if p.Status.Phase == v1.PodUnknown {
|
||
|
startupStatus.Unknown++
|
||
|
}
|
||
|
// Record count of scheduled pods (useful for computing scheduler throughput).
|
||
|
if p.Spec.NodeName != "" {
|
||
|
startupStatus.Scheduled++
|
||
|
}
|
||
|
}
|
||
|
return startupStatus
|
||
|
}
|
||
|
|
||
|
func (config *RCConfig) start() error {
|
||
|
// Don't force tests to fail if they don't care about containers restarting.
|
||
|
var maxContainerFailures int
|
||
|
if config.MaxContainerFailures == nil {
|
||
|
maxContainerFailures = int(math.Max(1.0, float64(config.Replicas)*.01))
|
||
|
} else {
|
||
|
maxContainerFailures = *config.MaxContainerFailures
|
||
|
}
|
||
|
|
||
|
label := labels.SelectorFromSet(labels.Set(map[string]string{"name": config.Name}))
|
||
|
|
||
|
ps, err := NewPodStore(config.Client, config.Namespace, label, fields.Everything())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer ps.Stop()
|
||
|
|
||
|
interval := config.PollInterval
|
||
|
if interval <= 0 {
|
||
|
interval = 10 * time.Second
|
||
|
}
|
||
|
timeout := config.Timeout
|
||
|
if timeout <= 0 {
|
||
|
timeout = 5 * time.Minute
|
||
|
}
|
||
|
oldPods := make([]*v1.Pod, 0)
|
||
|
oldRunning := 0
|
||
|
lastChange := time.Now()
|
||
|
for oldRunning != config.Replicas {
|
||
|
time.Sleep(interval)
|
||
|
|
||
|
pods := ps.List()
|
||
|
startupStatus := ComputeRCStartupStatus(pods, config.Replicas)
|
||
|
|
||
|
if config.CreatedPods != nil {
|
||
|
*config.CreatedPods = startupStatus.Created
|
||
|
}
|
||
|
if !config.Silent {
|
||
|
config.RCConfigLog(startupStatus.String(config.Name))
|
||
|
}
|
||
|
|
||
|
if config.PodStatusFile != nil {
|
||
|
fmt.Fprintf(config.PodStatusFile, "%d, running, %d, pending, %d, waiting, %d, inactive, %d, unknown, %d, runningButNotReady\n", startupStatus.Running, startupStatus.Pending, startupStatus.Waiting, startupStatus.Inactive, startupStatus.Unknown, startupStatus.RunningButNotReady)
|
||
|
}
|
||
|
|
||
|
if startupStatus.FailedContainers > maxContainerFailures {
|
||
|
if config.NodeDumpFunc != nil {
|
||
|
config.NodeDumpFunc(config.Client, startupStatus.ContainerRestartNodes.List(), config.RCConfigLog)
|
||
|
}
|
||
|
if config.ContainerDumpFunc != nil {
|
||
|
// Get the logs from the failed containers to help diagnose what caused them to fail
|
||
|
config.ContainerDumpFunc(config.Client, config.Namespace, config.RCConfigLog)
|
||
|
}
|
||
|
return fmt.Errorf("%d containers failed which is more than allowed %d", startupStatus.FailedContainers, maxContainerFailures)
|
||
|
}
|
||
|
|
||
|
diff := Diff(oldPods, pods)
|
||
|
deletedPods := diff.DeletedPods()
|
||
|
if len(deletedPods) != 0 {
|
||
|
// There are some pods that have disappeared.
|
||
|
err := fmt.Errorf("%d pods disappeared for %s: %v", len(deletedPods), config.Name, strings.Join(deletedPods, ", "))
|
||
|
config.RCConfigLog(err.Error())
|
||
|
config.RCConfigLog(diff.String(sets.NewString()))
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if len(pods) > len(oldPods) || startupStatus.Running > oldRunning {
|
||
|
lastChange = time.Now()
|
||
|
}
|
||
|
oldPods = pods
|
||
|
oldRunning = startupStatus.Running
|
||
|
|
||
|
if time.Since(lastChange) > timeout {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if oldRunning != config.Replicas {
|
||
|
// List only pods from a given replication controller.
|
||
|
options := metav1.ListOptions{LabelSelector: label.String()}
|
||
|
if pods, err := config.Client.CoreV1().Pods(metav1.NamespaceAll).List(options); err == nil {
|
||
|
for _, pod := range pods.Items {
|
||
|
config.RCConfigLog("Pod %s\t%s\t%s\t%s", pod.Name, pod.Spec.NodeName, pod.Status.Phase, pod.DeletionTimestamp)
|
||
|
}
|
||
|
} else {
|
||
|
config.RCConfigLog("Can't list pod debug info: %v", err)
|
||
|
}
|
||
|
return fmt.Errorf("Only %d pods started out of %d", oldRunning, config.Replicas)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Simplified version of RunRC, that does not create RC, but creates plain Pods.
|
||
|
// Optionally waits for pods to start running (if waitForRunning == true).
|
||
|
// The number of replicas must be non-zero.
|
||
|
func StartPods(c clientset.Interface, replicas int, namespace string, podNamePrefix string,
|
||
|
pod v1.Pod, waitForRunning bool, logFunc func(fmt string, args ...interface{})) error {
|
||
|
// no pod to start
|
||
|
if replicas < 1 {
|
||
|
panic("StartPods: number of replicas must be non-zero")
|
||
|
}
|
||
|
startPodsID := string(uuid.NewUUID()) // So that we can label and find them
|
||
|
for i := 0; i < replicas; i++ {
|
||
|
podName := fmt.Sprintf("%v-%v", podNamePrefix, i)
|
||
|
pod.ObjectMeta.Name = podName
|
||
|
pod.ObjectMeta.Labels["name"] = podName
|
||
|
pod.ObjectMeta.Labels["startPodsID"] = startPodsID
|
||
|
pod.Spec.Containers[0].Name = podName
|
||
|
if err := CreatePodWithRetries(c, namespace, &pod); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
logFunc("Waiting for running...")
|
||
|
if waitForRunning {
|
||
|
label := labels.SelectorFromSet(labels.Set(map[string]string{"startPodsID": startPodsID}))
|
||
|
err := WaitForPodsWithLabelRunning(c, namespace, label)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("Error waiting for %d pods to be running - probably a timeout: %v", replicas, err)
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Wait up to 10 minutes for all matching pods to become Running and at least one
|
||
|
// matching pod exists.
|
||
|
func WaitForPodsWithLabelRunning(c clientset.Interface, ns string, label labels.Selector) error {
|
||
|
return WaitForEnoughPodsWithLabelRunning(c, ns, label, -1)
|
||
|
}
|
||
|
|
||
|
// Wait up to 10 minutes for at least 'replicas' many pods to be Running and at least
|
||
|
// one matching pod exists. If 'replicas' is < 0, wait for all matching pods running.
|
||
|
func WaitForEnoughPodsWithLabelRunning(c clientset.Interface, ns string, label labels.Selector, replicas int) error {
|
||
|
running := false
|
||
|
ps, err := NewPodStore(c, ns, label, fields.Everything())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer ps.Stop()
|
||
|
|
||
|
for start := time.Now(); time.Since(start) < 10*time.Minute; time.Sleep(5 * time.Second) {
|
||
|
pods := ps.List()
|
||
|
if len(pods) == 0 {
|
||
|
continue
|
||
|
}
|
||
|
runningPodsCount := 0
|
||
|
for _, p := range pods {
|
||
|
if p.Status.Phase == v1.PodRunning {
|
||
|
runningPodsCount++
|
||
|
}
|
||
|
}
|
||
|
if (replicas < 0 && runningPodsCount < len(pods)) || (runningPodsCount < replicas) {
|
||
|
continue
|
||
|
}
|
||
|
running = true
|
||
|
break
|
||
|
}
|
||
|
if !running {
|
||
|
return fmt.Errorf("Timeout while waiting for pods with labels %q to be running", label.String())
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type CountToStrategy struct {
|
||
|
Count int
|
||
|
Strategy PrepareNodeStrategy
|
||
|
}
|
||
|
|
||
|
type TestNodePreparer interface {
|
||
|
PrepareNodes() error
|
||
|
CleanupNodes() error
|
||
|
}
|
||
|
|
||
|
type PrepareNodeStrategy interface {
|
||
|
PreparePatch(node *v1.Node) []byte
|
||
|
CleanupNode(node *v1.Node) *v1.Node
|
||
|
}
|
||
|
|
||
|
type TrivialNodePrepareStrategy struct{}
|
||
|
|
||
|
func (*TrivialNodePrepareStrategy) PreparePatch(*v1.Node) []byte {
|
||
|
return []byte{}
|
||
|
}
|
||
|
|
||
|
func (*TrivialNodePrepareStrategy) CleanupNode(node *v1.Node) *v1.Node {
|
||
|
nodeCopy := *node
|
||
|
return &nodeCopy
|
||
|
}
|
||
|
|
||
|
type LabelNodePrepareStrategy struct {
|
||
|
labelKey string
|
||
|
labelValue string
|
||
|
}
|
||
|
|
||
|
func NewLabelNodePrepareStrategy(labelKey string, labelValue string) *LabelNodePrepareStrategy {
|
||
|
return &LabelNodePrepareStrategy{
|
||
|
labelKey: labelKey,
|
||
|
labelValue: labelValue,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (s *LabelNodePrepareStrategy) PreparePatch(*v1.Node) []byte {
|
||
|
labelString := fmt.Sprintf("{\"%v\":\"%v\"}", s.labelKey, s.labelValue)
|
||
|
patch := fmt.Sprintf(`{"metadata":{"labels":%v}}`, labelString)
|
||
|
return []byte(patch)
|
||
|
}
|
||
|
|
||
|
func (s *LabelNodePrepareStrategy) CleanupNode(node *v1.Node) *v1.Node {
|
||
|
nodeCopy := node.DeepCopy()
|
||
|
if node.Labels != nil && len(node.Labels[s.labelKey]) != 0 {
|
||
|
delete(nodeCopy.Labels, s.labelKey)
|
||
|
}
|
||
|
return nodeCopy
|
||
|
}
|
||
|
|
||
|
func DoPrepareNode(client clientset.Interface, node *v1.Node, strategy PrepareNodeStrategy) error {
|
||
|
var err error
|
||
|
patch := strategy.PreparePatch(node)
|
||
|
if len(patch) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
for attempt := 0; attempt < retries; attempt++ {
|
||
|
if _, err = client.CoreV1().Nodes().Patch(node.Name, types.MergePatchType, []byte(patch)); err == nil {
|
||
|
return nil
|
||
|
}
|
||
|
if !apierrs.IsConflict(err) {
|
||
|
return fmt.Errorf("Error while applying patch %v to Node %v: %v", string(patch), node.Name, err)
|
||
|
}
|
||
|
time.Sleep(100 * time.Millisecond)
|
||
|
}
|
||
|
return fmt.Errorf("To many conflicts when applying patch %v to Node %v", string(patch), node.Name)
|
||
|
}
|
||
|
|
||
|
func DoCleanupNode(client clientset.Interface, nodeName string, strategy PrepareNodeStrategy) error {
|
||
|
for attempt := 0; attempt < retries; attempt++ {
|
||
|
node, err := client.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{})
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("Skipping cleanup of Node: failed to get Node %v: %v", nodeName, err)
|
||
|
}
|
||
|
updatedNode := strategy.CleanupNode(node)
|
||
|
if apiequality.Semantic.DeepEqual(node, updatedNode) {
|
||
|
return nil
|
||
|
}
|
||
|
if _, err = client.CoreV1().Nodes().Update(updatedNode); err == nil {
|
||
|
return nil
|
||
|
}
|
||
|
if !apierrs.IsConflict(err) {
|
||
|
return fmt.Errorf("Error when updating Node %v: %v", nodeName, err)
|
||
|
}
|
||
|
time.Sleep(100 * time.Millisecond)
|
||
|
}
|
||
|
return fmt.Errorf("To many conflicts when trying to cleanup Node %v", nodeName)
|
||
|
}
|
||
|
|
||
|
type TestPodCreateStrategy func(client clientset.Interface, namespace string, podCount int) error
|
||
|
|
||
|
type CountToPodStrategy struct {
|
||
|
Count int
|
||
|
Strategy TestPodCreateStrategy
|
||
|
}
|
||
|
|
||
|
type TestPodCreatorConfig map[string][]CountToPodStrategy
|
||
|
|
||
|
func NewTestPodCreatorConfig() *TestPodCreatorConfig {
|
||
|
config := make(TestPodCreatorConfig)
|
||
|
return &config
|
||
|
}
|
||
|
|
||
|
func (c *TestPodCreatorConfig) AddStrategy(
|
||
|
namespace string, podCount int, strategy TestPodCreateStrategy) {
|
||
|
(*c)[namespace] = append((*c)[namespace], CountToPodStrategy{Count: podCount, Strategy: strategy})
|
||
|
}
|
||
|
|
||
|
type TestPodCreator struct {
|
||
|
Client clientset.Interface
|
||
|
// namespace -> count -> strategy
|
||
|
Config *TestPodCreatorConfig
|
||
|
}
|
||
|
|
||
|
func NewTestPodCreator(client clientset.Interface, config *TestPodCreatorConfig) *TestPodCreator {
|
||
|
return &TestPodCreator{
|
||
|
Client: client,
|
||
|
Config: config,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *TestPodCreator) CreatePods() error {
|
||
|
for ns, v := range *(c.Config) {
|
||
|
for _, countToStrategy := range v {
|
||
|
if err := countToStrategy.Strategy(c.Client, ns, countToStrategy.Count); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func MakePodSpec() v1.PodSpec {
|
||
|
return v1.PodSpec{
|
||
|
Containers: []v1.Container{{
|
||
|
Name: "pause",
|
||
|
Image: "k8s.gcr.io/pause:3.1",
|
||
|
Ports: []v1.ContainerPort{{ContainerPort: 80}},
|
||
|
Resources: v1.ResourceRequirements{
|
||
|
Limits: v1.ResourceList{
|
||
|
v1.ResourceCPU: resource.MustParse("100m"),
|
||
|
v1.ResourceMemory: resource.MustParse("500Mi"),
|
||
|
},
|
||
|
Requests: v1.ResourceList{
|
||
|
v1.ResourceCPU: resource.MustParse("100m"),
|
||
|
v1.ResourceMemory: resource.MustParse("500Mi"),
|
||
|
},
|
||
|
},
|
||
|
}},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func makeCreatePod(client clientset.Interface, namespace string, podTemplate *v1.Pod) error {
|
||
|
if err := CreatePodWithRetries(client, namespace, podTemplate); err != nil {
|
||
|
return fmt.Errorf("Error creating pod: %v", err)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func CreatePod(client clientset.Interface, namespace string, podCount int, podTemplate *v1.Pod) error {
|
||
|
var createError error
|
||
|
lock := sync.Mutex{}
|
||
|
createPodFunc := func(i int) {
|
||
|
if err := makeCreatePod(client, namespace, podTemplate); err != nil {
|
||
|
lock.Lock()
|
||
|
defer lock.Unlock()
|
||
|
createError = err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if podCount < 30 {
|
||
|
workqueue.ParallelizeUntil(context.TODO(), podCount, podCount, createPodFunc)
|
||
|
} else {
|
||
|
workqueue.ParallelizeUntil(context.TODO(), 30, podCount, createPodFunc)
|
||
|
}
|
||
|
return createError
|
||
|
}
|
||
|
|
||
|
func createController(client clientset.Interface, controllerName, namespace string, podCount int, podTemplate *v1.Pod) error {
|
||
|
rc := &v1.ReplicationController{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Name: controllerName,
|
||
|
},
|
||
|
Spec: v1.ReplicationControllerSpec{
|
||
|
Replicas: func(i int) *int32 { x := int32(i); return &x }(podCount),
|
||
|
Selector: map[string]string{"name": controllerName},
|
||
|
Template: &v1.PodTemplateSpec{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Labels: map[string]string{"name": controllerName},
|
||
|
},
|
||
|
Spec: podTemplate.Spec,
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
if err := CreateRCWithRetries(client, namespace, rc); err != nil {
|
||
|
return fmt.Errorf("Error creating replication controller: %v", err)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func NewCustomCreatePodStrategy(podTemplate *v1.Pod) TestPodCreateStrategy {
|
||
|
return func(client clientset.Interface, namespace string, podCount int) error {
|
||
|
return CreatePod(client, namespace, podCount, podTemplate)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func NewSimpleCreatePodStrategy() TestPodCreateStrategy {
|
||
|
basePod := &v1.Pod{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
GenerateName: "simple-pod-",
|
||
|
},
|
||
|
Spec: MakePodSpec(),
|
||
|
}
|
||
|
return NewCustomCreatePodStrategy(basePod)
|
||
|
}
|
||
|
|
||
|
func NewSimpleWithControllerCreatePodStrategy(controllerName string) TestPodCreateStrategy {
|
||
|
return func(client clientset.Interface, namespace string, podCount int) error {
|
||
|
basePod := &v1.Pod{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
GenerateName: controllerName + "-pod-",
|
||
|
Labels: map[string]string{"name": controllerName},
|
||
|
},
|
||
|
Spec: MakePodSpec(),
|
||
|
}
|
||
|
if err := createController(client, controllerName, namespace, podCount, basePod); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return CreatePod(client, namespace, podCount, basePod)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type SecretConfig struct {
|
||
|
Content map[string]string
|
||
|
Client clientset.Interface
|
||
|
Name string
|
||
|
Namespace string
|
||
|
// If set this function will be used to print log lines instead of klog.
|
||
|
LogFunc func(fmt string, args ...interface{})
|
||
|
}
|
||
|
|
||
|
func (config *SecretConfig) Run() error {
|
||
|
secret := &v1.Secret{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Name: config.Name,
|
||
|
},
|
||
|
StringData: map[string]string{},
|
||
|
}
|
||
|
for k, v := range config.Content {
|
||
|
secret.StringData[k] = v
|
||
|
}
|
||
|
|
||
|
if err := CreateSecretWithRetries(config.Client, config.Namespace, secret); err != nil {
|
||
|
return fmt.Errorf("Error creating secret: %v", err)
|
||
|
}
|
||
|
config.LogFunc("Created secret %v/%v", config.Namespace, config.Name)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (config *SecretConfig) Stop() error {
|
||
|
if err := DeleteResourceWithRetries(config.Client, api.Kind("Secret"), config.Namespace, config.Name, &metav1.DeleteOptions{}); err != nil {
|
||
|
return fmt.Errorf("Error deleting secret: %v", err)
|
||
|
}
|
||
|
config.LogFunc("Deleted secret %v/%v", config.Namespace, config.Name)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// TODO: attach secrets using different possibilities: env vars, image pull secrets.
|
||
|
func attachSecrets(template *v1.PodTemplateSpec, secretNames []string) {
|
||
|
volumes := make([]v1.Volume, 0, len(secretNames))
|
||
|
mounts := make([]v1.VolumeMount, 0, len(secretNames))
|
||
|
for _, name := range secretNames {
|
||
|
volumes = append(volumes, v1.Volume{
|
||
|
Name: name,
|
||
|
VolumeSource: v1.VolumeSource{
|
||
|
Secret: &v1.SecretVolumeSource{
|
||
|
SecretName: name,
|
||
|
},
|
||
|
},
|
||
|
})
|
||
|
mounts = append(mounts, v1.VolumeMount{
|
||
|
Name: name,
|
||
|
MountPath: fmt.Sprintf("/%v", name),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
template.Spec.Volumes = volumes
|
||
|
template.Spec.Containers[0].VolumeMounts = mounts
|
||
|
}
|
||
|
|
||
|
type ConfigMapConfig struct {
|
||
|
Content map[string]string
|
||
|
Client clientset.Interface
|
||
|
Name string
|
||
|
Namespace string
|
||
|
// If set this function will be used to print log lines instead of klog.
|
||
|
LogFunc func(fmt string, args ...interface{})
|
||
|
}
|
||
|
|
||
|
func (config *ConfigMapConfig) Run() error {
|
||
|
configMap := &v1.ConfigMap{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Name: config.Name,
|
||
|
},
|
||
|
Data: map[string]string{},
|
||
|
}
|
||
|
for k, v := range config.Content {
|
||
|
configMap.Data[k] = v
|
||
|
}
|
||
|
|
||
|
if err := CreateConfigMapWithRetries(config.Client, config.Namespace, configMap); err != nil {
|
||
|
return fmt.Errorf("Error creating configmap: %v", err)
|
||
|
}
|
||
|
config.LogFunc("Created configmap %v/%v", config.Namespace, config.Name)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (config *ConfigMapConfig) Stop() error {
|
||
|
if err := DeleteResourceWithRetries(config.Client, api.Kind("ConfigMap"), config.Namespace, config.Name, &metav1.DeleteOptions{}); err != nil {
|
||
|
return fmt.Errorf("Error deleting configmap: %v", err)
|
||
|
}
|
||
|
config.LogFunc("Deleted configmap %v/%v", config.Namespace, config.Name)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// TODO: attach configmaps using different possibilities: env vars.
|
||
|
func attachConfigMaps(template *v1.PodTemplateSpec, configMapNames []string) {
|
||
|
volumes := make([]v1.Volume, 0, len(configMapNames))
|
||
|
mounts := make([]v1.VolumeMount, 0, len(configMapNames))
|
||
|
for _, name := range configMapNames {
|
||
|
volumes = append(volumes, v1.Volume{
|
||
|
Name: name,
|
||
|
VolumeSource: v1.VolumeSource{
|
||
|
ConfigMap: &v1.ConfigMapVolumeSource{
|
||
|
LocalObjectReference: v1.LocalObjectReference{
|
||
|
Name: name,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
})
|
||
|
mounts = append(mounts, v1.VolumeMount{
|
||
|
Name: name,
|
||
|
MountPath: fmt.Sprintf("/%v", name),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
template.Spec.Volumes = volumes
|
||
|
template.Spec.Containers[0].VolumeMounts = mounts
|
||
|
}
|
||
|
|
||
|
func attachServiceAccountTokenProjection(template *v1.PodTemplateSpec, name string) {
|
||
|
template.Spec.Containers[0].VolumeMounts = append(template.Spec.Containers[0].VolumeMounts,
|
||
|
v1.VolumeMount{
|
||
|
Name: name,
|
||
|
MountPath: "/var/service-account-tokens/" + name,
|
||
|
})
|
||
|
|
||
|
template.Spec.Volumes = append(template.Spec.Volumes,
|
||
|
v1.Volume{
|
||
|
Name: name,
|
||
|
VolumeSource: v1.VolumeSource{
|
||
|
Projected: &v1.ProjectedVolumeSource{
|
||
|
Sources: []v1.VolumeProjection{
|
||
|
{
|
||
|
ServiceAccountToken: &v1.ServiceAccountTokenProjection{
|
||
|
Path: "token",
|
||
|
Audience: name,
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
ConfigMap: &v1.ConfigMapProjection{
|
||
|
LocalObjectReference: v1.LocalObjectReference{
|
||
|
Name: "kube-root-ca-crt",
|
||
|
},
|
||
|
Items: []v1.KeyToPath{
|
||
|
{
|
||
|
Key: "ca.crt",
|
||
|
Path: "ca.crt",
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
DownwardAPI: &v1.DownwardAPIProjection{
|
||
|
Items: []v1.DownwardAPIVolumeFile{
|
||
|
{
|
||
|
Path: "namespace",
|
||
|
FieldRef: &v1.ObjectFieldSelector{
|
||
|
APIVersion: "v1",
|
||
|
FieldPath: "metadata.namespace",
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
})
|
||
|
}
|
||
|
|
||
|
type DaemonConfig struct {
|
||
|
Client clientset.Interface
|
||
|
Name string
|
||
|
Namespace string
|
||
|
Image string
|
||
|
// If set this function will be used to print log lines instead of klog.
|
||
|
LogFunc func(fmt string, args ...interface{})
|
||
|
// How long we wait for DaemonSet to become running.
|
||
|
Timeout time.Duration
|
||
|
}
|
||
|
|
||
|
func (config *DaemonConfig) Run() error {
|
||
|
if config.Image == "" {
|
||
|
config.Image = "k8s.gcr.io/pause:3.1"
|
||
|
}
|
||
|
nameLabel := map[string]string{
|
||
|
"name": config.Name + "-daemon",
|
||
|
}
|
||
|
daemon := &apps.DaemonSet{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Name: config.Name,
|
||
|
},
|
||
|
Spec: apps.DaemonSetSpec{
|
||
|
Template: v1.PodTemplateSpec{
|
||
|
ObjectMeta: metav1.ObjectMeta{
|
||
|
Labels: nameLabel,
|
||
|
},
|
||
|
Spec: v1.PodSpec{
|
||
|
Containers: []v1.Container{
|
||
|
{
|
||
|
Name: config.Name,
|
||
|
Image: config.Image,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
if err := CreateDaemonSetWithRetries(config.Client, config.Namespace, daemon); err != nil {
|
||
|
return fmt.Errorf("Error creating daemonset: %v", err)
|
||
|
}
|
||
|
|
||
|
var nodes *v1.NodeList
|
||
|
var err error
|
||
|
for i := 0; i < retries; i++ {
|
||
|
// Wait for all daemons to be running
|
||
|
nodes, err = config.Client.CoreV1().Nodes().List(metav1.ListOptions{ResourceVersion: "0"})
|
||
|
if err == nil {
|
||
|
break
|
||
|
} else if i+1 == retries {
|
||
|
return fmt.Errorf("Error listing Nodes while waiting for DaemonSet %v: %v", config.Name, err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
timeout := config.Timeout
|
||
|
if timeout <= 0 {
|
||
|
timeout = 5 * time.Minute
|
||
|
}
|
||
|
|
||
|
ps, err := NewPodStore(config.Client, config.Namespace, labels.SelectorFromSet(nameLabel), fields.Everything())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer ps.Stop()
|
||
|
|
||
|
err = wait.Poll(time.Second, timeout, func() (bool, error) {
|
||
|
pods := ps.List()
|
||
|
|
||
|
nodeHasDaemon := sets.NewString()
|
||
|
for _, pod := range pods {
|
||
|
podReady, _ := PodRunningReady(pod)
|
||
|
if pod.Spec.NodeName != "" && podReady {
|
||
|
nodeHasDaemon.Insert(pod.Spec.NodeName)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
running := len(nodeHasDaemon)
|
||
|
config.LogFunc("Found %v/%v Daemons %v running", running, config.Name, len(nodes.Items))
|
||
|
return running == len(nodes.Items), nil
|
||
|
})
|
||
|
if err != nil {
|
||
|
config.LogFunc("Timed out while waiting for DaemonsSet %v/%v to be running.", config.Namespace, config.Name)
|
||
|
} else {
|
||
|
config.LogFunc("Created Daemon %v/%v", config.Namespace, config.Name)
|
||
|
}
|
||
|
|
||
|
return err
|
||
|
}
|