func ValidateJobSpec(spec *extensions.JobSpec, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if spec.Parallelism != nil { allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.Parallelism), fldPath.Child("parallelism"))...) } if spec.Completions != nil { allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.Completions), fldPath.Child("completions"))...) } if spec.ActiveDeadlineSeconds != nil { allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.ActiveDeadlineSeconds), fldPath.Child("activeDeadlineSeconds"))...) } if spec.Selector == nil { allErrs = append(allErrs, field.Required(fldPath.Child("selector"), "")) } else { allErrs = append(allErrs, ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...) } if selector, err := extensions.LabelSelectorAsSelector(spec.Selector); err == nil { labels := labels.Set(spec.Template.Labels) if !selector.Matches(labels) { allErrs = append(allErrs, field.Invalid(fldPath.Child("template", "metadata", "labels"), spec.Template.Labels, "`selector` does not match template `labels`")) } } allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template, fldPath.Child("template"))...) if spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure && spec.Template.Spec.RestartPolicy != api.RestartPolicyNever { allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"), spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyOnFailure), string(api.RestartPolicyNever)})) } return allErrs }
// ValidateDaemonSetSpec tests if required fields in the DaemonSetSpec are set. func ValidateDaemonSetSpec(spec *extensions.DaemonSetSpec, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...) if spec.Template == nil { allErrs = append(allErrs, field.Required(fldPath.Child("template"))) return allErrs } selector, err := extensions.LabelSelectorAsSelector(spec.Selector) if err == nil && !selector.Matches(labels.Set(spec.Template.Labels)) { allErrs = append(allErrs, field.Invalid(fldPath.Child("template", "metadata", "labels"), spec.Template.Labels, "selector does not match template")) } allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(spec.Template, fldPath.Child("template"))...) // Daemons typically run on more than one node, so mark Read-Write persistent disks as invalid. allErrs = append(allErrs, apivalidation.ValidateReadOnlyPersistentDisks(spec.Template.Spec.Volumes, fldPath.Child("template", "spec", "volumes"))...) // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec(). if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways { allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"), spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)})) } return allErrs }
// ValidateDaemonSetSpec tests if required fields in the DaemonSetSpec are set. func ValidateDaemonSetSpec(spec *extensions.DaemonSetSpec, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} // The order of these checks is important because spec.Template is tested for nil value here // before accessing its labels in the following check. if spec.Template == nil { allErrs = append(allErrs, field.Required(fldPath.Child("template"), "")) return allErrs } allErrs = append(allErrs, ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...) selector, err := extensions.LabelSelectorAsSelector(spec.Selector) if err == nil && !selector.Matches(labels.Set(spec.Template.Labels)) { allErrs = append(allErrs, field.Invalid(fldPath.Child("template", "metadata", "labels"), spec.Template.Labels, "`selector` does not match template `labels`")) } allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(spec.Template, fldPath.Child("template"))...) // Daemons typically run on more than one node, so mark Read-Write persistent disks as invalid. allErrs = append(allErrs, apivalidation.ValidateReadOnlyPersistentDisks(spec.Template.Spec.Volumes, fldPath.Child("template", "spec", "volumes"))...) // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec(). if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways { allErrs = append(allErrs, field.NotSupported(fldPath.Child("template", "spec", "restartPolicy"), spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)})) } allErrs = append(allErrs, ValidateDaemonSetUpdateStrategy(&spec.UpdateStrategy, fldPath.Child("updateStrategy"))...) return allErrs }
// GetPodDaemonSets returns a list of daemon sets managing a pod. // Returns an error if and only if no matching daemon sets are found. func (s *StoreToDaemonSetLister) GetPodDaemonSets(pod *api.Pod) (daemonSets []extensions.DaemonSet, err error) { var selector labels.Selector var daemonSet extensions.DaemonSet if len(pod.Labels) == 0 { err = fmt.Errorf("no daemon sets found for pod %v because it has no labels", pod.Name) return } for _, m := range s.Store.List() { daemonSet = *m.(*extensions.DaemonSet) if daemonSet.Namespace != pod.Namespace { continue } selector, err = extensions.LabelSelectorAsSelector(daemonSet.Spec.Selector) if err != nil { // this should not happen if the DaemonSet passed validation return nil, err } // If a daemonSet with a nil or empty selector creeps in, it should match nothing, not everything. if selector.Empty() || !selector.Matches(labels.Set(pod.Labels)) { continue } daemonSets = append(daemonSets, daemonSet) } if len(daemonSets) == 0 { err = fmt.Errorf("could not find daemon set for pod %s in namespace %s with labels: %v", pod.Name, pod.Namespace, pod.Labels) } return }
// GetPodJobs returns a list of jobs managing a pod. Returns an error only if no matching jobs are found. func (s *StoreToJobLister) GetPodJobs(pod *api.Pod) (jobs []extensions.Job, err error) { var selector labels.Selector var job extensions.Job if len(pod.Labels) == 0 { err = fmt.Errorf("no jobs found for pod %v because it has no labels", pod.Name) return } for _, m := range s.Store.List() { job = *m.(*extensions.Job) if job.Namespace != pod.Namespace { continue } selector, _ = extensions.LabelSelectorAsSelector(job.Spec.Selector) if !selector.Matches(labels.Set(pod.Labels)) { continue } jobs = append(jobs, job) } if len(jobs) == 0 { err = fmt.Errorf("could not find jobs for pod %s in namespace %s with labels: %v", pod.Name, pod.Namespace, pod.Labels) } return }
// GetPodReplicaSets returns a list of ReplicaSets managing a pod. Returns an error only if no matching ReplicaSets are found. func (s *StoreToReplicaSetLister) GetPodReplicaSets(pod *api.Pod) (rss []extensions.ReplicaSet, err error) { var selector labels.Selector var rs extensions.ReplicaSet if len(pod.Labels) == 0 { err = fmt.Errorf("no ReplicaSets found for pod %v because it has no labels", pod.Name) return } for _, m := range s.Store.List() { rs = *m.(*extensions.ReplicaSet) if rs.Namespace != pod.Namespace { continue } selector, err = extensions.LabelSelectorAsSelector(rs.Spec.Selector) if err != nil { err = fmt.Errorf("failed to convert pod selector to selector: %v", err) return } // If a ReplicaSet with a nil or empty selector creeps in, it should match nothing, not everything. if selector.Empty() || !selector.Matches(labels.Set(pod.Labels)) { continue } rss = append(rss, rs) } if len(rss) == 0 { err = fmt.Errorf("could not find ReplicaSet for pod %s in namespace %s with labels: %v", pod.Name, pod.Namespace, pod.Labels) } return }
// Transforms simple selector map to labels.Selector object that can be used when querying for // object. func toLabelSelector(selector map[string]string) (labels.Selector, error) { labelSelector, err := extensions.LabelSelectorAsSelector(&extensions.LabelSelector{MatchLabels: selector}) if err != nil { return nil, err } return labelSelector, nil }
func (reaper *JobReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *api.DeleteOptions) error { jobs := reaper.Extensions().Jobs(namespace) pods := reaper.Pods(namespace) scaler, err := ScalerFor(extensions.Kind("Job"), *reaper) if err != nil { return err } job, err := jobs.Get(name) if err != nil { return err } if timeout == 0 { // we will never have more active pods than job.Spec.Parallelism parallelism := *job.Spec.Parallelism timeout = Timeout + time.Duration(10*parallelism)*time.Second } // TODO: handle overlapping jobs retry := NewRetryParams(reaper.pollInterval, reaper.timeout) waitForJobs := NewRetryParams(reaper.pollInterval, timeout) if err = scaler.Scale(namespace, name, 0, nil, retry, waitForJobs); err != nil { return err } // at this point only dead pods are left, that should be removed selector, _ := extensions.LabelSelectorAsSelector(job.Spec.Selector) options := api.ListOptions{LabelSelector: selector} podList, err := pods.List(options) if err != nil { return err } errList := []error{} for _, pod := range podList.Items { if err := pods.Delete(pod.Name, gracePeriod); err != nil { // ignores the error when the pod isn't found if !errors.IsNotFound(err) { errList = append(errList, err) } } } if len(errList) > 0 { return utilerrors.NewAggregate(errList) } // once we have all the pods removed we can safely remove the job itself if err := jobs.Delete(name, gracePeriod); err != nil { return err } return nil }
// getNodesToDaemonSetPods returns a map from nodes to daemon pods (corresponding to ds) running on the nodes. func (dsc *DaemonSetsController) getNodesToDaemonPods(ds *extensions.DaemonSet) (map[string][]*api.Pod, error) { nodeToDaemonPods := make(map[string][]*api.Pod) selector, err := extensions.LabelSelectorAsSelector(ds.Spec.Selector) if err != nil { return nil, err } daemonPods, err := dsc.podStore.Pods(ds.Namespace).List(selector) if err != nil { return nodeToDaemonPods, err } for i := range daemonPods.Items { nodeName := daemonPods.Items[i].Spec.NodeName nodeToDaemonPods[nodeName] = append(nodeToDaemonPods[nodeName], &daemonPods.Items[i]) } return nodeToDaemonPods, nil }
// ValidateReplicaSetSpec tests if required fields in the ReplicaSet spec are set. func ValidateReplicaSetSpec(spec *extensions.ReplicaSetSpec, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(spec.Replicas), fldPath.Child("replicas"))...) if spec.Selector == nil { allErrs = append(allErrs, field.Required(fldPath.Child("selector"), "")) } else { allErrs = append(allErrs, ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...) if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 { allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is not valid for deployment.")) } } selector, err := extensions.LabelSelectorAsSelector(spec.Selector) if err != nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "failed to convert LabelSelector to Selector.")) } else { allErrs = append(allErrs, ValidatePodTemplateSpecForReplicaSet(spec.Template, selector, spec.Replicas, fldPath.Child("template"))...) } return allErrs }
// syncReplicaSet will sync the ReplicaSet with the given key if it has had its expectations fulfilled, // meaning it did not expect to see any more of its pods created or deleted. This function is not meant to be // invoked concurrently with the same key. func (rsc *ReplicaSetController) syncReplicaSet(key string) error { startTime := time.Now() defer func() { glog.V(4).Infof("Finished syncing replica set %q (%v)", key, time.Now().Sub(startTime)) }() obj, exists, err := rsc.rsStore.Store.GetByKey(key) if !exists { glog.Infof("ReplicaSet has been deleted %v", key) rsc.expectations.DeleteExpectations(key) return nil } if err != nil { glog.Infof("Unable to retrieve ReplicaSet %v from store: %v", key, err) rsc.queue.Add(key) return err } rs := *obj.(*extensions.ReplicaSet) if !rsc.podStoreSynced() { // Sleep so we give the pod reflector goroutine a chance to run. time.Sleep(PodStoreSyncedPollPeriod) glog.Infof("Waiting for pods controller to sync, requeuing ReplicaSet %v", rs.Name) rsc.enqueueReplicaSet(&rs) return nil } // Check the expectations of the ReplicaSet before counting active pods, otherwise a new pod can sneak // in and update the expectations after we've retrieved active pods from the store. If a new pod enters // the store after we've checked the expectation, the ReplicaSet sync is just deferred till the next // relist. rsKey, err := controller.KeyFunc(&rs) if err != nil { glog.Errorf("Couldn't get key for ReplicaSet %#v: %v", rs, err) return err } rsNeedsSync := rsc.expectations.SatisfiedExpectations(rsKey) selector, err := extensions.LabelSelectorAsSelector(rs.Spec.Selector) if err != nil { glog.Errorf("Error converting pod selector to selector: %v", err) return err } podList, err := rsc.podStore.Pods(rs.Namespace).List(selector) if err != nil { glog.Errorf("Error getting pods for ReplicaSet %q: %v", key, err) rsc.queue.Add(key) return err } // TODO: Do this in a single pass, or use an index. filteredPods := controller.FilterActivePods(podList.Items) if rsNeedsSync { rsc.manageReplicas(filteredPods, &rs) } // Always updates status as pods come up or die. if err := updateReplicaCount(rsc.kubeClient.Extensions().ReplicaSets(rs.Namespace), rs, len(filteredPods)); err != nil { // Multiple things could lead to this update failing. Requeuing the replica set ensures // we retry with some fairness. glog.V(2).Infof("Failed to update replica count for controller %v/%v; requeuing; error: %v", rs.Namespace, rs.Name, err) rsc.enqueueReplicaSet(&rs) } return nil }
// syncJob will sync the job with the given key if it has had its expectations fulfilled, meaning // it did not expect to see any more of its pods created or deleted. This function is not meant to be invoked // concurrently with the same key. func (jm *JobController) syncJob(key string) error { startTime := time.Now() defer func() { glog.V(4).Infof("Finished syncing job %q (%v)", key, time.Now().Sub(startTime)) }() obj, exists, err := jm.jobStore.Store.GetByKey(key) if !exists { glog.V(4).Infof("Job has been deleted: %v", key) jm.expectations.DeleteExpectations(key) return nil } if err != nil { glog.Errorf("Unable to retrieve job %v from store: %v", key, err) jm.queue.Add(key) return err } job := *obj.(*extensions.Job) if !jm.podStoreSynced() { // Sleep so we give the pod reflector goroutine a chance to run. time.Sleep(replicationcontroller.PodStoreSyncedPollPeriod) glog.V(4).Infof("Waiting for pods controller to sync, requeuing job %v", job.Name) jm.enqueueController(&job) return nil } // Check the expectations of the job before counting active pods, otherwise a new pod can sneak in // and update the expectations after we've retrieved active pods from the store. If a new pod enters // the store after we've checked the expectation, the job sync is just deferred till the next relist. jobKey, err := controller.KeyFunc(&job) if err != nil { glog.Errorf("Couldn't get key for job %#v: %v", job, err) return err } jobNeedsSync := jm.expectations.SatisfiedExpectations(jobKey) selector, _ := extensions.LabelSelectorAsSelector(job.Spec.Selector) podList, err := jm.podStore.Pods(job.Namespace).List(selector) if err != nil { glog.Errorf("Error getting pods for job %q: %v", key, err) jm.queue.Add(key) return err } activePods := controller.FilterActivePods(podList.Items) active := len(activePods) succeeded, failed := getStatus(podList.Items) conditions := len(job.Status.Conditions) if job.Status.StartTime == nil { now := unversioned.Now() job.Status.StartTime = &now } if pastActiveDeadline(&job) { // if job was finished previously, we don't want to redo the termination if isJobFinished(&job) { return nil } // TODO: below code should be replaced with pod termination resulting in // pod failures, rather than killing pods. Unfortunately none such solution // exists ATM. There's an open discussion in the topic in // https://github.com/kubernetes/kubernetes/issues/14602 which might give // some sort of solution to above problem. // kill remaining active pods wait := sync.WaitGroup{} wait.Add(active) for i := 0; i < active; i++ { go func(ix int) { defer wait.Done() if err := jm.podControl.DeletePod(job.Namespace, activePods[ix].Name); err != nil { defer util.HandleError(err) } }(i) } wait.Wait() // update status values accordingly failed += active active = 0 job.Status.Conditions = append(job.Status.Conditions, newCondition(extensions.JobFailed, "DeadlineExceeded", "Job was active longer than specified deadline")) jm.recorder.Event(&job, api.EventTypeNormal, "DeadlineExceeded", "Job was active longer than specified deadline") } else { if jobNeedsSync { active = jm.manageJob(activePods, succeeded, &job) } completions := succeeded if completions == *job.Spec.Completions { job.Status.Conditions = append(job.Status.Conditions, newCondition(extensions.JobComplete, "", "")) now := unversioned.Now() job.Status.CompletionTime = &now } } // no need to update the job if the status hasn't changed since last time if job.Status.Active != active || job.Status.Succeeded != succeeded || job.Status.Failed != failed || len(job.Status.Conditions) != conditions { job.Status.Active = active job.Status.Succeeded = succeeded job.Status.Failed = failed if err := jm.updateHandler(&job); err != nil { glog.Errorf("Failed to update job %v, requeuing. Error: %v", job.Name, err) jm.enqueueController(&job) } } return nil }
// syncJob will sync the job with the given key if it has had its expectations fulfilled, meaning // it did not expect to see any more of its pods created or deleted. This function is not meant to be invoked // concurrently with the same key. func (jm *JobController) syncJob(key string) error { startTime := time.Now() defer func() { glog.V(4).Infof("Finished syncing job %q (%v)", key, time.Now().Sub(startTime)) }() obj, exists, err := jm.jobStore.Store.GetByKey(key) if !exists { glog.V(4).Infof("Job has been deleted: %v", key) jm.expectations.DeleteExpectations(key) return nil } if err != nil { glog.Errorf("Unable to retrieve job %v from store: %v", key, err) jm.queue.Add(key) return err } job := *obj.(*extensions.Job) if !jm.podStoreSynced() { // Sleep so we give the pod reflector goroutine a chance to run. time.Sleep(replicationcontroller.PodStoreSyncedPollPeriod) glog.V(4).Infof("Waiting for pods controller to sync, requeuing job %v", job.Name) jm.enqueueController(&job) return nil } // Check the expectations of the job before counting active pods, otherwise a new pod can sneak in // and update the expectations after we've retrieved active pods from the store. If a new pod enters // the store after we've checked the expectation, the job sync is just deferred till the next relist. jobKey, err := controller.KeyFunc(&job) if err != nil { glog.Errorf("Couldn't get key for job %#v: %v", job, err) return err } jobNeedsSync := jm.expectations.SatisfiedExpectations(jobKey) selector, _ := extensions.LabelSelectorAsSelector(job.Spec.Selector) podList, err := jm.podStore.Pods(job.Namespace).List(selector) if err != nil { glog.Errorf("Error getting pods for job %q: %v", key, err) jm.queue.Add(key) return err } activePods := controller.FilterActivePods(podList.Items) active := len(activePods) succeeded, failed := getStatus(podList.Items) if jobNeedsSync { active = jm.manageJob(activePods, succeeded, &job) } completions := succeeded if completions == *job.Spec.Completions { job.Status.Conditions = append(job.Status.Conditions, newCondition()) } // no need to update the job if the status hasn't changed since last time if job.Status.Active != active || job.Status.Succeeded != succeeded || job.Status.Failed != failed { job.Status.Active = active job.Status.Succeeded = succeeded job.Status.Failed = failed if err := jm.updateHandler(&job); err != nil { glog.Errorf("Failed to update job %v, requeuing. Error: %v", job.Name, err) jm.enqueueController(&job) } } return nil }