mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Job controller logic
This commit is contained in:
		@@ -38,6 +38,7 @@ import (
 | 
				
			|||||||
	"k8s.io/kubernetes/pkg/controller/daemon"
 | 
						"k8s.io/kubernetes/pkg/controller/daemon"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/controller/deployment"
 | 
						"k8s.io/kubernetes/pkg/controller/deployment"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/controller/endpoint"
 | 
						"k8s.io/kubernetes/pkg/controller/endpoint"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/controller/job"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/controller/namespace"
 | 
						"k8s.io/kubernetes/pkg/controller/namespace"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/controller/node"
 | 
						"k8s.io/kubernetes/pkg/controller/node"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/controller/persistentvolume"
 | 
						"k8s.io/kubernetes/pkg/controller/persistentvolume"
 | 
				
			||||||
@@ -66,6 +67,7 @@ type CMServer struct {
 | 
				
			|||||||
	ConcurrentEndpointSyncs           int
 | 
						ConcurrentEndpointSyncs           int
 | 
				
			||||||
	ConcurrentRCSyncs                 int
 | 
						ConcurrentRCSyncs                 int
 | 
				
			||||||
	ConcurrentDSCSyncs                int
 | 
						ConcurrentDSCSyncs                int
 | 
				
			||||||
 | 
						ConcurrentJobSyncs                int
 | 
				
			||||||
	ServiceSyncPeriod                 time.Duration
 | 
						ServiceSyncPeriod                 time.Duration
 | 
				
			||||||
	NodeSyncPeriod                    time.Duration
 | 
						NodeSyncPeriod                    time.Duration
 | 
				
			||||||
	ResourceQuotaSyncPeriod           time.Duration
 | 
						ResourceQuotaSyncPeriod           time.Duration
 | 
				
			||||||
@@ -104,6 +106,7 @@ func NewCMServer() *CMServer {
 | 
				
			|||||||
		ConcurrentEndpointSyncs:           5,
 | 
							ConcurrentEndpointSyncs:           5,
 | 
				
			||||||
		ConcurrentRCSyncs:                 5,
 | 
							ConcurrentRCSyncs:                 5,
 | 
				
			||||||
		ConcurrentDSCSyncs:                2,
 | 
							ConcurrentDSCSyncs:                2,
 | 
				
			||||||
 | 
							ConcurrentJobSyncs:                5,
 | 
				
			||||||
		ServiceSyncPeriod:                 5 * time.Minute,
 | 
							ServiceSyncPeriod:                 5 * time.Minute,
 | 
				
			||||||
		NodeSyncPeriod:                    10 * time.Second,
 | 
							NodeSyncPeriod:                    10 * time.Second,
 | 
				
			||||||
		ResourceQuotaSyncPeriod:           10 * time.Second,
 | 
							ResourceQuotaSyncPeriod:           10 * time.Second,
 | 
				
			||||||
@@ -238,6 +241,9 @@ func (s *CMServer) Run(_ []string) error {
 | 
				
			|||||||
	go daemon.NewDaemonSetsController(kubeClient).
 | 
						go daemon.NewDaemonSetsController(kubeClient).
 | 
				
			||||||
		Run(s.ConcurrentDSCSyncs, util.NeverStop)
 | 
							Run(s.ConcurrentDSCSyncs, util.NeverStop)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go job.NewJobManager(kubeClient).
 | 
				
			||||||
 | 
							Run(s.ConcurrentJobSyncs, util.NeverStop)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cloud, err := cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile)
 | 
						cloud, err := cloudprovider.InitCloudProvider(s.CloudProvider, s.CloudConfigFile)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		glog.Fatalf("Cloud provider could not be initialized: %v", err)
 | 
							glog.Fatalf("Cloud provider could not be initialized: %v", err)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -139,6 +139,13 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
 | 
				
			|||||||
				j.RollingUpdate = &rollingUpdate
 | 
									j.RollingUpdate = &rollingUpdate
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							func(j *experimental.JobSpec, c fuzz.Continue) {
 | 
				
			||||||
 | 
								c.FuzzNoCustom(j) // fuzz self without calling this function again
 | 
				
			||||||
 | 
								completions := c.Rand.Int()
 | 
				
			||||||
 | 
								parallelism := c.Rand.Int()
 | 
				
			||||||
 | 
								j.Completions = &completions
 | 
				
			||||||
 | 
								j.Parallelism = ¶llelism
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		func(j *api.List, c fuzz.Continue) {
 | 
							func(j *api.List, c fuzz.Continue) {
 | 
				
			||||||
			c.FuzzNoCustom(j) // fuzz self without calling this function again
 | 
								c.FuzzNoCustom(j) // fuzz self without calling this function again
 | 
				
			||||||
			// TODO: uncomment when round trip starts from a versioned object
 | 
								// TODO: uncomment when round trip starts from a versioned object
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -75,5 +75,15 @@ func addDefaultingFuncs() {
 | 
				
			|||||||
				*obj.Spec.UniqueLabelKey = "deployment.kubernetes.io/podTemplateHash"
 | 
									*obj.Spec.UniqueLabelKey = "deployment.kubernetes.io/podTemplateHash"
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							func(obj *Job) {
 | 
				
			||||||
 | 
								if obj.Spec.Completions == nil {
 | 
				
			||||||
 | 
									completions := 1
 | 
				
			||||||
 | 
									obj.Spec.Completions = &completions
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if obj.Spec.Parallelism == nil {
 | 
				
			||||||
 | 
									parallelism := 2
 | 
				
			||||||
 | 
									obj.Spec.Parallelism = ¶llelism
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -189,6 +189,43 @@ func TestSetDefaultDeployment(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestSetDefaultJob(t *testing.T) {
 | 
				
			||||||
 | 
						expected := &Job{
 | 
				
			||||||
 | 
							Spec: JobSpec{
 | 
				
			||||||
 | 
								Completions: newInt(1),
 | 
				
			||||||
 | 
								Parallelism: newInt(2),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tests := []*Job{
 | 
				
			||||||
 | 
							{},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Spec: JobSpec{
 | 
				
			||||||
 | 
									Completions: newInt(1),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Spec: JobSpec{
 | 
				
			||||||
 | 
									Parallelism: newInt(2),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, original := range tests {
 | 
				
			||||||
 | 
							obj2 := roundTrip(t, runtime.Object(original))
 | 
				
			||||||
 | 
							got, ok := obj2.(*Job)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								t.Errorf("unexpected object: %v", got)
 | 
				
			||||||
 | 
								t.FailNow()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if *got.Spec.Completions != *expected.Spec.Completions {
 | 
				
			||||||
 | 
								t.Errorf("got different completions than expected: %d %d", *got.Spec.Completions, *expected.Spec.Completions)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if *got.Spec.Parallelism != *expected.Spec.Parallelism {
 | 
				
			||||||
 | 
								t.Errorf("got different parallelism than expected: %d %d", *got.Spec.Parallelism, *expected.Spec.Parallelism)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func roundTrip(t *testing.T, obj runtime.Object) runtime.Object {
 | 
					func roundTrip(t *testing.T, obj runtime.Object) runtime.Object {
 | 
				
			||||||
	data, err := v1.Codec.Encode(obj)
 | 
						data, err := v1.Codec.Encode(obj)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										52
									
								
								pkg/client/cache/listers.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										52
									
								
								pkg/client/cache/listers.go
									
									
									
									
										vendored
									
									
								
							@@ -344,3 +344,55 @@ func (s *StoreToEndpointsLister) GetServiceEndpoints(svc *api.Service) (ep api.E
 | 
				
			|||||||
	err = fmt.Errorf("Could not find endpoints for service: %v", svc.Name)
 | 
						err = fmt.Errorf("Could not find endpoints for service: %v", svc.Name)
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// StoreToJobLister gives a store List and Exists methods. The store must contain only Jobs.
 | 
				
			||||||
 | 
					type StoreToJobLister struct {
 | 
				
			||||||
 | 
						Store
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Exists checks if the given job exists in the store.
 | 
				
			||||||
 | 
					func (s *StoreToJobLister) Exists(job *experimental.Job) (bool, error) {
 | 
				
			||||||
 | 
						_, exists, err := s.Store.Get(job)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return exists, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// StoreToJobLister lists all jobs in the store.
 | 
				
			||||||
 | 
					func (s *StoreToJobLister) List() (jobs []experimental.Job, err error) {
 | 
				
			||||||
 | 
						for _, c := range s.Store.List() {
 | 
				
			||||||
 | 
							jobs = append(jobs, *(c.(*experimental.Job)))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return jobs, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetPodControllers 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 []experimental.Job, err error) {
 | 
				
			||||||
 | 
						var selector labels.Selector
 | 
				
			||||||
 | 
						var job experimental.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.(*experimental.Job)
 | 
				
			||||||
 | 
							if job.Namespace != pod.Namespace {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							labelSet := labels.Set(job.Spec.Selector)
 | 
				
			||||||
 | 
							selector = labels.Set(job.Spec.Selector).AsSelector()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Job with a nil or empty selector match nothing
 | 
				
			||||||
 | 
							if labelSet.AsSelector().Empty() || !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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										86
									
								
								pkg/client/unversioned/testclient/fake_jobs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								pkg/client/unversioned/testclient/fake_jobs.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 testclient
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/api"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/apis/experimental"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/fields"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/labels"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/watch"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FakeJobs implements JobInterface. Meant to be embedded into a struct to get a default
 | 
				
			||||||
 | 
					// implementation. This makes faking out just the method you want to test easier.
 | 
				
			||||||
 | 
					type FakeJobs struct {
 | 
				
			||||||
 | 
						Fake      *FakeExperimental
 | 
				
			||||||
 | 
						Namespace string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *FakeJobs) Get(name string) (*experimental.Job, error) {
 | 
				
			||||||
 | 
						obj, err := c.Fake.Invokes(NewGetAction("jobs", c.Namespace, name), &experimental.Job{})
 | 
				
			||||||
 | 
						if obj == nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return obj.(*experimental.Job), err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *FakeJobs) List(label labels.Selector, fields fields.Selector) (*experimental.JobList, error) {
 | 
				
			||||||
 | 
						obj, err := c.Fake.Invokes(NewListAction("jobs", c.Namespace, label, nil), &experimental.JobList{})
 | 
				
			||||||
 | 
						if obj == nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return obj.(*experimental.JobList), err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *FakeJobs) Create(job *experimental.Job) (*experimental.Job, error) {
 | 
				
			||||||
 | 
						obj, err := c.Fake.Invokes(NewCreateAction("jobs", c.Namespace, job), job)
 | 
				
			||||||
 | 
						if obj == nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return obj.(*experimental.Job), err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *FakeJobs) Update(job *experimental.Job) (*experimental.Job, error) {
 | 
				
			||||||
 | 
						obj, err := c.Fake.Invokes(NewUpdateAction("jobs", c.Namespace, job), job)
 | 
				
			||||||
 | 
						if obj == nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return obj.(*experimental.Job), err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *FakeJobs) Delete(name string, options *api.DeleteOptions) error {
 | 
				
			||||||
 | 
						_, err := c.Fake.Invokes(NewDeleteAction("jobs", c.Namespace, name), &experimental.Job{})
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *FakeJobs) Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error) {
 | 
				
			||||||
 | 
						return c.Fake.InvokesWatch(NewWatchAction("jobs", c.Namespace, label, field, resourceVersion))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *FakeJobs) UpdateStatus(job *experimental.Job) (result *experimental.Job, err error) {
 | 
				
			||||||
 | 
						obj, err := c.Fake.Invokes(NewUpdateSubresourceAction("jobs", "status", c.Namespace, job), job)
 | 
				
			||||||
 | 
						if obj == nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return obj.(*experimental.Job), err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -263,5 +263,5 @@ func (c *FakeExperimental) Scales(namespace string) client.ScaleInterface {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *FakeExperimental) Jobs(namespace string) client.JobInterface {
 | 
					func (c *FakeExperimental) Jobs(namespace string) client.JobInterface {
 | 
				
			||||||
	panic("unimplemented")
 | 
						return &FakeJobs{Fake: c, Namespace: namespace}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,7 +26,6 @@ import (
 | 
				
			|||||||
	"k8s.io/kubernetes/pkg/api"
 | 
						"k8s.io/kubernetes/pkg/api"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/api/latest"
 | 
						"k8s.io/kubernetes/pkg/api/latest"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/api/validation"
 | 
						"k8s.io/kubernetes/pkg/api/validation"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/apis/experimental"
 | 
					 | 
				
			||||||
	"k8s.io/kubernetes/pkg/client/cache"
 | 
						"k8s.io/kubernetes/pkg/client/cache"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/client/record"
 | 
						"k8s.io/kubernetes/pkg/client/record"
 | 
				
			||||||
	client "k8s.io/kubernetes/pkg/client/unversioned"
 | 
						client "k8s.io/kubernetes/pkg/client/unversioned"
 | 
				
			||||||
@@ -213,10 +212,10 @@ func NewControllerExpectations() *ControllerExpectations {
 | 
				
			|||||||
// PodControlInterface is an interface that knows how to add or delete pods
 | 
					// PodControlInterface is an interface that knows how to add or delete pods
 | 
				
			||||||
// created as an interface to allow testing.
 | 
					// created as an interface to allow testing.
 | 
				
			||||||
type PodControlInterface interface {
 | 
					type PodControlInterface interface {
 | 
				
			||||||
	// CreateReplica creates new replicated pods according to the spec.
 | 
						// CreatePods creates new pods according to the spec.
 | 
				
			||||||
	CreateReplica(namespace string, controller *api.ReplicationController) error
 | 
						CreatePods(namespace string, template *api.PodTemplateSpec, object runtime.Object) error
 | 
				
			||||||
	// CreateReplicaOnNode creates a new pod according to the spec on the specified node.
 | 
						// CreatePodsOnNode creates a new pod accorting to the spec on the specified node.
 | 
				
			||||||
	CreateReplicaOnNode(namespace string, ds *experimental.DaemonSet, nodeName string) error
 | 
						CreatePodsOnNode(nodeName, namespace string, template *api.PodTemplateSpec, object runtime.Object) error
 | 
				
			||||||
	// DeletePod deletes the pod identified by podID.
 | 
						// DeletePod deletes the pod identified by podID.
 | 
				
			||||||
	DeletePod(namespace string, podID string) error
 | 
						DeletePod(namespace string, podID string) error
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -227,7 +226,7 @@ type RealPodControl struct {
 | 
				
			|||||||
	Recorder   record.EventRecorder
 | 
						Recorder   record.EventRecorder
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getReplicaLabelSet(template *api.PodTemplateSpec) labels.Set {
 | 
					func getPodsLabelSet(template *api.PodTemplateSpec) labels.Set {
 | 
				
			||||||
	desiredLabels := make(labels.Set)
 | 
						desiredLabels := make(labels.Set)
 | 
				
			||||||
	for k, v := range template.Labels {
 | 
						for k, v := range template.Labels {
 | 
				
			||||||
		desiredLabels[k] = v
 | 
							desiredLabels[k] = v
 | 
				
			||||||
@@ -235,7 +234,7 @@ func getReplicaLabelSet(template *api.PodTemplateSpec) labels.Set {
 | 
				
			|||||||
	return desiredLabels
 | 
						return desiredLabels
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getReplicaAnnotationSet(template *api.PodTemplateSpec, object runtime.Object) (labels.Set, error) {
 | 
					func getPodsAnnotationSet(template *api.PodTemplateSpec, object runtime.Object) (labels.Set, error) {
 | 
				
			||||||
	desiredAnnotations := make(labels.Set)
 | 
						desiredAnnotations := make(labels.Set)
 | 
				
			||||||
	for k, v := range template.Annotations {
 | 
						for k, v := range template.Annotations {
 | 
				
			||||||
		desiredAnnotations[k] = v
 | 
							desiredAnnotations[k] = v
 | 
				
			||||||
@@ -254,7 +253,7 @@ func getReplicaAnnotationSet(template *api.PodTemplateSpec, object runtime.Objec
 | 
				
			|||||||
	return desiredAnnotations, nil
 | 
						return desiredAnnotations, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getReplicaPrefix(controllerName string) string {
 | 
					func getPodsPrefix(controllerName string) string {
 | 
				
			||||||
	// use the dash (if the name isn't too long) to make the pod name a bit prettier
 | 
						// use the dash (if the name isn't too long) to make the pod name a bit prettier
 | 
				
			||||||
	prefix := fmt.Sprintf("%s-", controllerName)
 | 
						prefix := fmt.Sprintf("%s-", controllerName)
 | 
				
			||||||
	if ok, _ := validation.ValidatePodName(prefix, true); !ok {
 | 
						if ok, _ := validation.ValidatePodName(prefix, true); !ok {
 | 
				
			||||||
@@ -263,44 +262,25 @@ func getReplicaPrefix(controllerName string) string {
 | 
				
			|||||||
	return prefix
 | 
						return prefix
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (r RealPodControl) CreateReplica(namespace string, controller *api.ReplicationController) error {
 | 
					func (r RealPodControl) CreatePods(namespace string, template *api.PodTemplateSpec, object runtime.Object) error {
 | 
				
			||||||
	desiredLabels := getReplicaLabelSet(controller.Spec.Template)
 | 
						return r.createPods("", namespace, template, object)
 | 
				
			||||||
	desiredAnnotations, err := getReplicaAnnotationSet(controller.Spec.Template, controller)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	prefix := getReplicaPrefix(controller.Name)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pod := &api.Pod{
 | 
					 | 
				
			||||||
		ObjectMeta: api.ObjectMeta{
 | 
					 | 
				
			||||||
			Labels:       desiredLabels,
 | 
					 | 
				
			||||||
			Annotations:  desiredAnnotations,
 | 
					 | 
				
			||||||
			GenerateName: prefix,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if err := api.Scheme.Convert(&controller.Spec.Template.Spec, &pod.Spec); err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("unable to convert pod template: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if labels.Set(pod.Labels).AsSelector().Empty() {
 | 
					 | 
				
			||||||
		return fmt.Errorf("unable to create pod replica, no labels")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if newPod, err := r.KubeClient.Pods(namespace).Create(pod); err != nil {
 | 
					 | 
				
			||||||
		r.Recorder.Eventf(controller, "FailedCreate", "Error creating: %v", err)
 | 
					 | 
				
			||||||
		return fmt.Errorf("unable to create pod replica: %v", err)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		glog.V(4).Infof("Controller %v created pod %v", controller.Name, newPod.Name)
 | 
					 | 
				
			||||||
		r.Recorder.Eventf(controller, "SuccessfulCreate", "Created pod: %v", newPod.Name)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (r RealPodControl) CreateReplicaOnNode(namespace string, ds *experimental.DaemonSet, nodeName string) error {
 | 
					func (r RealPodControl) CreatePodsOnNode(nodeName, namespace string, template *api.PodTemplateSpec, object runtime.Object) error {
 | 
				
			||||||
	desiredLabels := getReplicaLabelSet(ds.Spec.Template)
 | 
						return r.createPods(nodeName, namespace, template, object)
 | 
				
			||||||
	desiredAnnotations, err := getReplicaAnnotationSet(ds.Spec.Template, ds)
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r RealPodControl) createPods(nodeName, namespace string, template *api.PodTemplateSpec, object runtime.Object) error {
 | 
				
			||||||
 | 
						desiredLabels := getPodsLabelSet(template)
 | 
				
			||||||
 | 
						desiredAnnotations, err := getPodsAnnotationSet(template, object)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	prefix := getReplicaPrefix(ds.Name)
 | 
						meta, err := api.ObjectMetaFor(object)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("object does not have ObjectMeta, %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						prefix := getPodsPrefix(meta.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pod := &api.Pod{
 | 
						pod := &api.Pod{
 | 
				
			||||||
		ObjectMeta: api.ObjectMeta{
 | 
							ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
@@ -309,22 +289,22 @@ func (r RealPodControl) CreateReplicaOnNode(namespace string, ds *experimental.D
 | 
				
			|||||||
			GenerateName: prefix,
 | 
								GenerateName: prefix,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if err := api.Scheme.Convert(&ds.Spec.Template.Spec, &pod.Spec); err != nil {
 | 
						if len(nodeName) != 0 {
 | 
				
			||||||
 | 
							pod.Spec.NodeName = nodeName
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := api.Scheme.Convert(&template.Spec, &pod.Spec); err != nil {
 | 
				
			||||||
		return fmt.Errorf("unable to convert pod template: %v", err)
 | 
							return fmt.Errorf("unable to convert pod template: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// if a pod does not have labels then it cannot be controlled by any controller
 | 
					 | 
				
			||||||
	if labels.Set(pod.Labels).AsSelector().Empty() {
 | 
						if labels.Set(pod.Labels).AsSelector().Empty() {
 | 
				
			||||||
		return fmt.Errorf("unable to create pod replica, no labels")
 | 
							return fmt.Errorf("unable to create pods, no labels")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	pod.Spec.NodeName = nodeName
 | 
					 | 
				
			||||||
	if newPod, err := r.KubeClient.Pods(namespace).Create(pod); err != nil {
 | 
						if newPod, err := r.KubeClient.Pods(namespace).Create(pod); err != nil {
 | 
				
			||||||
		r.Recorder.Eventf(ds, "FailedCreate", "Error creating: %v", err)
 | 
							r.Recorder.Eventf(object, "FailedCreate", "Error creating: %v", err)
 | 
				
			||||||
		return fmt.Errorf("unable to create pod replica: %v", err)
 | 
							return fmt.Errorf("unable to create pods: %v", err)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		glog.V(4).Infof("Controller %v created pod %v", ds.Name, newPod.Name)
 | 
							glog.V(4).Infof("Controller %v created pod %v", meta.Name, newPod.Name)
 | 
				
			||||||
		r.Recorder.Eventf(ds, "SuccessfulCreate", "Created pod: %v", newPod.Name)
 | 
							r.Recorder.Eventf(object, "SuccessfulCreate", "Created pod: %v", newPod.Name)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -180,7 +180,7 @@ func TestControllerExpectations(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestCreateReplica(t *testing.T) {
 | 
					func TestCreatePods(t *testing.T) {
 | 
				
			||||||
	ns := api.NamespaceDefault
 | 
						ns := api.NamespaceDefault
 | 
				
			||||||
	body := runtime.EncodeOrDie(testapi.Default.Codec(), &api.Pod{ObjectMeta: api.ObjectMeta{Name: "empty_pod"}})
 | 
						body := runtime.EncodeOrDie(testapi.Default.Codec(), &api.Pod{ObjectMeta: api.ObjectMeta{Name: "empty_pod"}})
 | 
				
			||||||
	fakeHandler := util.FakeHandler{
 | 
						fakeHandler := util.FakeHandler{
 | 
				
			||||||
@@ -199,7 +199,7 @@ func TestCreateReplica(t *testing.T) {
 | 
				
			|||||||
	controllerSpec := newReplicationController(1)
 | 
						controllerSpec := newReplicationController(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Make sure createReplica sends a POST to the apiserver with a pod from the controllers pod template
 | 
						// Make sure createReplica sends a POST to the apiserver with a pod from the controllers pod template
 | 
				
			||||||
	podControl.CreateReplica(ns, controllerSpec)
 | 
						podControl.CreatePods(ns, controllerSpec.Spec.Template, controllerSpec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	expectedPod := api.Pod{
 | 
						expectedPod := api.Pod{
 | 
				
			||||||
		ObjectMeta: api.ObjectMeta{
 | 
							ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -371,7 +371,7 @@ func (dsc *DaemonSetsController) manage(ds *experimental.DaemonSet) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	glog.V(4).Infof("Nodes needing daemon pods for daemon set %s: %+v", ds.Name, nodesNeedingDaemonPods)
 | 
						glog.V(4).Infof("Nodes needing daemon pods for daemon set %s: %+v", ds.Name, nodesNeedingDaemonPods)
 | 
				
			||||||
	for i := range nodesNeedingDaemonPods {
 | 
						for i := range nodesNeedingDaemonPods {
 | 
				
			||||||
		if err := dsc.podControl.CreateReplicaOnNode(ds.Namespace, ds, nodesNeedingDaemonPods[i]); err != nil {
 | 
							if err := dsc.podControl.CreatePodsOnNode(nodesNeedingDaemonPods[i], ds.Namespace, ds.Spec.Template, ds); err != nil {
 | 
				
			||||||
			glog.V(2).Infof("Failed creation, decrementing expectations for set %q/%q", ds.Namespace, ds.Name)
 | 
								glog.V(2).Infof("Failed creation, decrementing expectations for set %q/%q", ds.Namespace, ds.Name)
 | 
				
			||||||
			dsc.expectations.CreationObserved(dsKey)
 | 
								dsc.expectations.CreationObserved(dsKey)
 | 
				
			||||||
			util.HandleError(err)
 | 
								util.HandleError(err)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,6 +27,7 @@ import (
 | 
				
			|||||||
	"k8s.io/kubernetes/pkg/client/cache"
 | 
						"k8s.io/kubernetes/pkg/client/cache"
 | 
				
			||||||
	client "k8s.io/kubernetes/pkg/client/unversioned"
 | 
						client "k8s.io/kubernetes/pkg/client/unversioned"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/controller"
 | 
						"k8s.io/kubernetes/pkg/controller"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/runtime"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/securitycontext"
 | 
						"k8s.io/kubernetes/pkg/securitycontext"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -38,7 +39,7 @@ var (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type FakePodControl struct {
 | 
					type FakePodControl struct {
 | 
				
			||||||
	daemonSet     []experimental.DaemonSet
 | 
						podSpec       []api.PodTemplateSpec
 | 
				
			||||||
	deletePodName []string
 | 
						deletePodName []string
 | 
				
			||||||
	lock          sync.Mutex
 | 
						lock          sync.Mutex
 | 
				
			||||||
	err           error
 | 
						err           error
 | 
				
			||||||
@@ -48,17 +49,17 @@ func init() {
 | 
				
			|||||||
	api.ForTesting_ReferencesAllowBlankSelfLinks = true
 | 
						api.ForTesting_ReferencesAllowBlankSelfLinks = true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (f *FakePodControl) CreateReplica(namespace string, spec *api.ReplicationController) error {
 | 
					func (f *FakePodControl) CreatePods(namespace string, spec *api.PodTemplateSpec, object runtime.Object) error {
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (f *FakePodControl) CreateReplicaOnNode(namespace string, ds *experimental.DaemonSet, nodeName string) error {
 | 
					func (f *FakePodControl) CreatePodsOnNode(nodeName, namespace string, spec *api.PodTemplateSpec, object runtime.Object) error {
 | 
				
			||||||
	f.lock.Lock()
 | 
						f.lock.Lock()
 | 
				
			||||||
	defer f.lock.Unlock()
 | 
						defer f.lock.Unlock()
 | 
				
			||||||
	if f.err != nil {
 | 
						if f.err != nil {
 | 
				
			||||||
		return f.err
 | 
							return f.err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	f.daemonSet = append(f.daemonSet, *ds)
 | 
						f.podSpec = append(f.podSpec, *spec)
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -75,7 +76,7 @@ func (f *FakePodControl) clear() {
 | 
				
			|||||||
	f.lock.Lock()
 | 
						f.lock.Lock()
 | 
				
			||||||
	defer f.lock.Unlock()
 | 
						defer f.lock.Unlock()
 | 
				
			||||||
	f.deletePodName = []string{}
 | 
						f.deletePodName = []string{}
 | 
				
			||||||
	f.daemonSet = []experimental.DaemonSet{}
 | 
						f.podSpec = []api.PodTemplateSpec{}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func newDaemonSet(name string) *experimental.DaemonSet {
 | 
					func newDaemonSet(name string) *experimental.DaemonSet {
 | 
				
			||||||
@@ -164,8 +165,8 @@ func newTestController() (*DaemonSetsController, *FakePodControl) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func validateSyncDaemonSets(t *testing.T, fakePodControl *FakePodControl, expectedCreates, expectedDeletes int) {
 | 
					func validateSyncDaemonSets(t *testing.T, fakePodControl *FakePodControl, expectedCreates, expectedDeletes int) {
 | 
				
			||||||
	if len(fakePodControl.daemonSet) != expectedCreates {
 | 
						if len(fakePodControl.podSpec) != expectedCreates {
 | 
				
			||||||
		t.Errorf("Unexpected number of creates.  Expected %d, saw %d\n", expectedCreates, len(fakePodControl.daemonSet))
 | 
							t.Errorf("Unexpected number of creates.  Expected %d, saw %d\n", expectedCreates, len(fakePodControl.podSpec))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if len(fakePodControl.deletePodName) != expectedDeletes {
 | 
						if len(fakePodControl.deletePodName) != expectedDeletes {
 | 
				
			||||||
		t.Errorf("Unexpected number of deletes.  Expected %d, saw %d\n", expectedDeletes, len(fakePodControl.deletePodName))
 | 
							t.Errorf("Unexpected number of deletes.  Expected %d, saw %d\n", expectedDeletes, len(fakePodControl.deletePodName))
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										18
									
								
								pkg/controller/job/doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								pkg/controller/job/doc.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 job contains logic for watching and synchronizing jobs.
 | 
				
			||||||
 | 
					package job
 | 
				
			||||||
							
								
								
									
										452
									
								
								pkg/controller/job/job_controller.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										452
									
								
								pkg/controller/job/job_controller.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,452 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 job
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"sort"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/golang/glog"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/api"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/apis/experimental"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/client/cache"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/client/record"
 | 
				
			||||||
 | 
						client "k8s.io/kubernetes/pkg/client/unversioned"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/controller"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/controller/framework"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/controller/replication"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/fields"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/labels"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/runtime"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/util"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/util/workqueue"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/watch"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type JobManager struct {
 | 
				
			||||||
 | 
						kubeClient client.Interface
 | 
				
			||||||
 | 
						podControl controller.PodControlInterface
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// To allow injection of updateJob for testing.
 | 
				
			||||||
 | 
						updateHandler func(job *experimental.Job) error
 | 
				
			||||||
 | 
						syncHandler   func(jobKey string) error
 | 
				
			||||||
 | 
						// podStoreSynced returns true if the pod store has been synced at least once.
 | 
				
			||||||
 | 
						// Added as a member to the struct to allow injection for testing.
 | 
				
			||||||
 | 
						podStoreSynced func() bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// A TTLCache of pod creates/deletes each rc expects to see
 | 
				
			||||||
 | 
						expectations controller.ControllerExpectationsInterface
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// A store of job, populated by the jobController
 | 
				
			||||||
 | 
						jobStore cache.StoreToJobLister
 | 
				
			||||||
 | 
						// Watches changes to all jobs
 | 
				
			||||||
 | 
						jobController *framework.Controller
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// A store of pods, populated by the podController
 | 
				
			||||||
 | 
						podStore cache.StoreToPodLister
 | 
				
			||||||
 | 
						// Watches changes to all pods
 | 
				
			||||||
 | 
						podController *framework.Controller
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Jobs that need to be updated
 | 
				
			||||||
 | 
						queue *workqueue.Type
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewJobManager(kubeClient client.Interface) *JobManager {
 | 
				
			||||||
 | 
						eventBroadcaster := record.NewBroadcaster()
 | 
				
			||||||
 | 
						eventBroadcaster.StartLogging(glog.Infof)
 | 
				
			||||||
 | 
						eventBroadcaster.StartRecordingToSink(kubeClient.Events(""))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						jm := &JobManager{
 | 
				
			||||||
 | 
							kubeClient: kubeClient,
 | 
				
			||||||
 | 
							podControl: controller.RealPodControl{
 | 
				
			||||||
 | 
								KubeClient: kubeClient,
 | 
				
			||||||
 | 
								Recorder:   eventBroadcaster.NewRecorder(api.EventSource{Component: "job"}),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							expectations: controller.NewControllerExpectations(),
 | 
				
			||||||
 | 
							queue:        workqueue.New(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						jm.jobStore.Store, jm.jobController = framework.NewInformer(
 | 
				
			||||||
 | 
							&cache.ListWatch{
 | 
				
			||||||
 | 
								ListFunc: func() (runtime.Object, error) {
 | 
				
			||||||
 | 
									return jm.kubeClient.Experimental().Jobs(api.NamespaceAll).List(labels.Everything(), fields.Everything())
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								WatchFunc: func(rv string) (watch.Interface, error) {
 | 
				
			||||||
 | 
									return jm.kubeClient.Experimental().Jobs(api.NamespaceAll).Watch(labels.Everything(), fields.Everything(), rv)
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&experimental.Job{},
 | 
				
			||||||
 | 
							replicationcontroller.FullControllerResyncPeriod,
 | 
				
			||||||
 | 
							framework.ResourceEventHandlerFuncs{
 | 
				
			||||||
 | 
								AddFunc: jm.enqueueController,
 | 
				
			||||||
 | 
								UpdateFunc: func(old, cur interface{}) {
 | 
				
			||||||
 | 
									job := cur.(*experimental.Job)
 | 
				
			||||||
 | 
									for _, c := range job.Status.Conditions {
 | 
				
			||||||
 | 
										if c.Type == experimental.JobComplete && c.Status == api.ConditionTrue {
 | 
				
			||||||
 | 
											return
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									jm.enqueueController(cur)
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								DeleteFunc: jm.enqueueController,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						jm.podStore.Store, jm.podController = framework.NewInformer(
 | 
				
			||||||
 | 
							&cache.ListWatch{
 | 
				
			||||||
 | 
								ListFunc: func() (runtime.Object, error) {
 | 
				
			||||||
 | 
									return jm.kubeClient.Pods(api.NamespaceAll).List(labels.Everything(), fields.Everything())
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								WatchFunc: func(rv string) (watch.Interface, error) {
 | 
				
			||||||
 | 
									return jm.kubeClient.Pods(api.NamespaceAll).Watch(labels.Everything(), fields.Everything(), rv)
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&api.Pod{},
 | 
				
			||||||
 | 
							replicationcontroller.PodRelistPeriod,
 | 
				
			||||||
 | 
							framework.ResourceEventHandlerFuncs{
 | 
				
			||||||
 | 
								AddFunc:    jm.addPod,
 | 
				
			||||||
 | 
								UpdateFunc: jm.updatePod,
 | 
				
			||||||
 | 
								DeleteFunc: jm.deletePod,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						jm.updateHandler = jm.updateJob
 | 
				
			||||||
 | 
						jm.syncHandler = jm.syncJob
 | 
				
			||||||
 | 
						jm.podStoreSynced = jm.podController.HasSynced
 | 
				
			||||||
 | 
						return jm
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Run the main goroutine responsible for watching and syncing jobs.
 | 
				
			||||||
 | 
					func (jm *JobManager) Run(workers int, stopCh <-chan struct{}) {
 | 
				
			||||||
 | 
						defer util.HandleCrash()
 | 
				
			||||||
 | 
						go jm.jobController.Run(stopCh)
 | 
				
			||||||
 | 
						go jm.podController.Run(stopCh)
 | 
				
			||||||
 | 
						for i := 0; i < workers; i++ {
 | 
				
			||||||
 | 
							go util.Until(jm.worker, time.Second, stopCh)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						<-stopCh
 | 
				
			||||||
 | 
						glog.Infof("Shutting down Job Manager")
 | 
				
			||||||
 | 
						jm.queue.ShutDown()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// getPodJob returns the job managing the given pod.
 | 
				
			||||||
 | 
					func (jm *JobManager) getPodJob(pod *api.Pod) *experimental.Job {
 | 
				
			||||||
 | 
						jobs, err := jm.jobStore.GetPodJobs(pod)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							glog.V(4).Infof("No jobs found for pod %v, job manager will avoid syncing", pod.Name)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// TODO: add sorting and rethink the overlaping controllers, internally and with RCs
 | 
				
			||||||
 | 
						return &jobs[0]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// When a pod is created, enqueue the controller that manages it and update it's expectations.
 | 
				
			||||||
 | 
					func (jm *JobManager) addPod(obj interface{}) {
 | 
				
			||||||
 | 
						pod := obj.(*api.Pod)
 | 
				
			||||||
 | 
						if pod.DeletionTimestamp != nil {
 | 
				
			||||||
 | 
							// on a restart of the controller manager, it's possible a new pod shows up in a state that
 | 
				
			||||||
 | 
							// is already pending deletion. Prevent the pod from being a creation observation.
 | 
				
			||||||
 | 
							jm.deletePod(pod)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if job := jm.getPodJob(pod); job != nil {
 | 
				
			||||||
 | 
							jobKey, err := controller.KeyFunc(job)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								glog.Errorf("Couldn't get key for job %#v: %v", job, err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							jm.expectations.CreationObserved(jobKey)
 | 
				
			||||||
 | 
							jm.enqueueController(job)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// When a pod is updated, figure out what job/s manage it and wake them up.
 | 
				
			||||||
 | 
					// If the labels of the pod have changed we need to awaken both the old
 | 
				
			||||||
 | 
					// and new job. old and cur must be *api.Pod types.
 | 
				
			||||||
 | 
					func (jm *JobManager) updatePod(old, cur interface{}) {
 | 
				
			||||||
 | 
						if api.Semantic.DeepEqual(old, cur) {
 | 
				
			||||||
 | 
							// A periodic relist will send update events for all known pods.
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						curPod := cur.(*api.Pod)
 | 
				
			||||||
 | 
						if curPod.DeletionTimestamp != nil {
 | 
				
			||||||
 | 
							// when a pod is deleted gracefully it's deletion timestamp is first modified to reflect a grace period,
 | 
				
			||||||
 | 
							// and after such time has passed, the kubelet actually deletes it from the store. We receive an update
 | 
				
			||||||
 | 
							// for modification of the deletion timestamp and expect an job to create more pods asap, not wait
 | 
				
			||||||
 | 
							// until the kubelet actually deletes the pod.
 | 
				
			||||||
 | 
							jm.deletePod(curPod)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if job := jm.getPodJob(curPod); job != nil {
 | 
				
			||||||
 | 
							jm.enqueueController(job)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						oldPod := old.(*api.Pod)
 | 
				
			||||||
 | 
						// Only need to get the old job if the labels changed.
 | 
				
			||||||
 | 
						if !reflect.DeepEqual(curPod.Labels, oldPod.Labels) {
 | 
				
			||||||
 | 
							// If the old and new job are the same, the first one that syncs
 | 
				
			||||||
 | 
							// will set expectations preventing any damage from the second.
 | 
				
			||||||
 | 
							if oldJob := jm.getPodJob(oldPod); oldJob != nil {
 | 
				
			||||||
 | 
								jm.enqueueController(oldJob)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// When a pod is deleted, enqueue the job that manages the pod and update its expectations.
 | 
				
			||||||
 | 
					// obj could be an *api.Pod, or a DeletionFinalStateUnknown marker item.
 | 
				
			||||||
 | 
					func (jm *JobManager) deletePod(obj interface{}) {
 | 
				
			||||||
 | 
						pod, ok := obj.(*api.Pod)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// When a delete is dropped, the relist will notice a pod in the store not
 | 
				
			||||||
 | 
						// in the list, leading to the insertion of a tombstone object which contains
 | 
				
			||||||
 | 
						// the deleted key/value. Note that this value might be stale. If the pod
 | 
				
			||||||
 | 
						// changed labels the new job will not be woken up till the periodic resync.
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								glog.Errorf("Couldn't get object from tombstone %+v, could take up to %v before a job recreates a pod", obj, controller.ExpectationsTimeout)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							pod, ok = tombstone.Obj.(*api.Pod)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								glog.Errorf("Tombstone contained object that is not a pod %+v, could take up to %v before job recreates a pod", obj, controller.ExpectationsTimeout)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if job := jm.getPodJob(pod); job != nil {
 | 
				
			||||||
 | 
							jobKey, err := controller.KeyFunc(job)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								glog.Errorf("Couldn't get key for job %#v: %v", job, err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							jm.expectations.DeletionObserved(jobKey)
 | 
				
			||||||
 | 
							jm.enqueueController(job)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// obj could be an *experimental.Job, or a DeletionFinalStateUnknown marker item.
 | 
				
			||||||
 | 
					func (jm *JobManager) enqueueController(obj interface{}) {
 | 
				
			||||||
 | 
						key, err := controller.KeyFunc(obj)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							glog.Errorf("Couldn't get key for object %+v: %v", obj, err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: Handle overlapping controllers better. Either disallow them at admission time or
 | 
				
			||||||
 | 
						// deterministically avoid syncing controllers that fight over pods. Currently, we only
 | 
				
			||||||
 | 
						// ensure that the same controller is synced for a given pod. When we periodically relist
 | 
				
			||||||
 | 
						// all controllers there will still be some replica instability. One way to handle this is
 | 
				
			||||||
 | 
						// by querying the store for all controllers that this rc overlaps, as well as all
 | 
				
			||||||
 | 
						// controllers that overlap this rc, and sorting them.
 | 
				
			||||||
 | 
						jm.queue.Add(key)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// worker runs a worker thread that just dequeues items, processes them, and marks them done.
 | 
				
			||||||
 | 
					// It enforces that the syncHandler is never invoked concurrently with the same key.
 | 
				
			||||||
 | 
					func (jm *JobManager) worker() {
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							func() {
 | 
				
			||||||
 | 
								key, quit := jm.queue.Get()
 | 
				
			||||||
 | 
								if quit {
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								defer jm.queue.Done(key)
 | 
				
			||||||
 | 
								err := jm.syncHandler(key.(string))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									glog.Errorf("Error syncing job: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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 *JobManager) 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.Infof("Job has been deleted %v", key)
 | 
				
			||||||
 | 
							jm.expectations.DeleteExpectations(key)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							glog.Infof("Unable to retrieve job %v from store: %v", key, err)
 | 
				
			||||||
 | 
							jm.queue.Add(key)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						job := *obj.(*experimental.Job)
 | 
				
			||||||
 | 
						if !jm.podStoreSynced() {
 | 
				
			||||||
 | 
							// Sleep so we give the pod reflector goroutine a chance to run.
 | 
				
			||||||
 | 
							time.Sleep(replicationcontroller.PodStoreSyncedPollPeriod)
 | 
				
			||||||
 | 
							glog.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)
 | 
				
			||||||
 | 
						podList, err := jm.podStore.Pods(job.Namespace).List(labels.Set(job.Spec.Selector).AsSelector())
 | 
				
			||||||
 | 
						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)
 | 
				
			||||||
 | 
						successful, unsuccessful := getStatus(jobKey, job.Spec.Template.Spec.RestartPolicy, podList.Items)
 | 
				
			||||||
 | 
						if jobNeedsSync {
 | 
				
			||||||
 | 
							active = jm.manageJob(activePods, successful, unsuccessful, &job)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						completions := successful
 | 
				
			||||||
 | 
						if job.Spec.Template.Spec.RestartPolicy == api.RestartPolicyNever {
 | 
				
			||||||
 | 
							completions += unsuccessful
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						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.Successful != successful || job.Status.Unsuccessful != unsuccessful {
 | 
				
			||||||
 | 
							job.Status.Active = active
 | 
				
			||||||
 | 
							job.Status.Successful = successful
 | 
				
			||||||
 | 
							job.Status.Unsuccessful = unsuccessful
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := jm.updateHandler(&job); err != nil {
 | 
				
			||||||
 | 
								glog.V(2).Infof("Failed to update job %v, requeuing", job.Name)
 | 
				
			||||||
 | 
								jm.enqueueController(&job)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func newCondition() experimental.JobCondition {
 | 
				
			||||||
 | 
						return experimental.JobCondition{
 | 
				
			||||||
 | 
							Type:               experimental.JobComplete,
 | 
				
			||||||
 | 
							Status:             api.ConditionTrue,
 | 
				
			||||||
 | 
							LastProbeTime:      util.Now(),
 | 
				
			||||||
 | 
							LastTransitionTime: util.Now(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getStatus(jobKey string, restartPolicy api.RestartPolicy, pods []api.Pod) (successful, unsuccessful int) {
 | 
				
			||||||
 | 
						successful = filterPods(pods, api.PodSucceeded)
 | 
				
			||||||
 | 
						if restartPolicy == api.RestartPolicyNever {
 | 
				
			||||||
 | 
							unsuccessful = filterPods(pods, api.PodFailed)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (jm *JobManager) manageJob(activePods []*api.Pod, successful, unsuccessful int, job *experimental.Job) int {
 | 
				
			||||||
 | 
						active := len(activePods)
 | 
				
			||||||
 | 
						parallelism := *job.Spec.Parallelism
 | 
				
			||||||
 | 
						jobKey, err := controller.KeyFunc(job)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							glog.Errorf("Couldn't get key for job %#v: %v", job, err)
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if active > parallelism {
 | 
				
			||||||
 | 
							diff := active - parallelism
 | 
				
			||||||
 | 
							jm.expectations.ExpectDeletions(jobKey, diff)
 | 
				
			||||||
 | 
							glog.V(2).Infof("Too many pods running job %q, need %d, deleting %d", jobKey, parallelism, diff)
 | 
				
			||||||
 | 
							// Sort the pods in the order such that not-ready < ready, unscheduled
 | 
				
			||||||
 | 
							// < scheduled, and pending < running. This ensures that we delete pods
 | 
				
			||||||
 | 
							// in the earlier stages whenever possible.
 | 
				
			||||||
 | 
							sort.Sort(controller.ActivePods(activePods))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							active -= diff
 | 
				
			||||||
 | 
							wait := sync.WaitGroup{}
 | 
				
			||||||
 | 
							wait.Add(diff)
 | 
				
			||||||
 | 
							for i := 0; i < diff; i++ {
 | 
				
			||||||
 | 
								go func(ix int) {
 | 
				
			||||||
 | 
									defer wait.Done()
 | 
				
			||||||
 | 
									if err := jm.podControl.DeletePod(job.Namespace, activePods[ix].Name); err != nil {
 | 
				
			||||||
 | 
										// Decrement the expected number of deletes because the informer won't observe this deletion
 | 
				
			||||||
 | 
										glog.V(2).Infof("Failed deletion, decrementing expectations for controller %q", jobKey)
 | 
				
			||||||
 | 
										jm.expectations.DeletionObserved(jobKey)
 | 
				
			||||||
 | 
										util.HandleError(err)
 | 
				
			||||||
 | 
										active++
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}(i)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							wait.Wait()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						} else if active < parallelism {
 | 
				
			||||||
 | 
							// how many executions are left to run
 | 
				
			||||||
 | 
							diff := *job.Spec.Completions - successful
 | 
				
			||||||
 | 
							// for RestartPolicyNever we need to count unsuccessful pods as well
 | 
				
			||||||
 | 
							if job.Spec.Template.Spec.RestartPolicy == api.RestartPolicyNever {
 | 
				
			||||||
 | 
								diff -= unsuccessful
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// limit to parallelism and count active pods as well
 | 
				
			||||||
 | 
							if diff > parallelism {
 | 
				
			||||||
 | 
								diff = parallelism
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							diff -= active
 | 
				
			||||||
 | 
							jm.expectations.ExpectCreations(jobKey, diff)
 | 
				
			||||||
 | 
							glog.V(2).Infof("Too few pods running job %q, need %d, creating %d", jobKey, parallelism, diff)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							active += diff
 | 
				
			||||||
 | 
							wait := sync.WaitGroup{}
 | 
				
			||||||
 | 
							wait.Add(diff)
 | 
				
			||||||
 | 
							for i := 0; i < diff; i++ {
 | 
				
			||||||
 | 
								go func() {
 | 
				
			||||||
 | 
									defer wait.Done()
 | 
				
			||||||
 | 
									if err := jm.podControl.CreatePods(job.Namespace, job.Spec.Template, job); err != nil {
 | 
				
			||||||
 | 
										// Decrement the expected number of creates because the informer won't observe this pod
 | 
				
			||||||
 | 
										glog.V(2).Infof("Failed creation, decrementing expectations for controller %q", jobKey)
 | 
				
			||||||
 | 
										jm.expectations.CreationObserved(jobKey)
 | 
				
			||||||
 | 
										util.HandleError(err)
 | 
				
			||||||
 | 
										active--
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							wait.Wait()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return active
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (jm *JobManager) updateJob(job *experimental.Job) error {
 | 
				
			||||||
 | 
						_, err := jm.kubeClient.Experimental().Jobs(job.Namespace).Update(job)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// filterPods returns pods based on their phase.
 | 
				
			||||||
 | 
					func filterPods(pods []api.Pod, phase api.PodPhase) int {
 | 
				
			||||||
 | 
						result := 0
 | 
				
			||||||
 | 
						for i := range pods {
 | 
				
			||||||
 | 
							if phase == pods[i].Status.Phase {
 | 
				
			||||||
 | 
								result++
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										553
									
								
								pkg/controller/job/job_controller_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										553
									
								
								pkg/controller/job/job_controller_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,553 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2015 The Kubernetes Authors All rights reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 job
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/api"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/api/testapi"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/apis/experimental"
 | 
				
			||||||
 | 
						client "k8s.io/kubernetes/pkg/client/unversioned"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/client/unversioned/testclient"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/controller"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/runtime"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/util"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/watch"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Give each test that starts a background controller up to 1/2 a second.
 | 
				
			||||||
 | 
					// Since we need to start up a goroutine to test watch, this routine needs
 | 
				
			||||||
 | 
					// to get cpu before the test can complete. If the test is starved of cpu,
 | 
				
			||||||
 | 
					// the watch test will take up to 1/2 a second before timing out.
 | 
				
			||||||
 | 
					const controllerTimeout = 500 * time.Millisecond
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var alwaysReady = func() bool { return true }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FakePodControl struct {
 | 
				
			||||||
 | 
						podSpec       []api.PodTemplateSpec
 | 
				
			||||||
 | 
						deletePodName []string
 | 
				
			||||||
 | 
						lock          sync.Mutex
 | 
				
			||||||
 | 
						err           error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (f *FakePodControl) CreatePods(namespace string, spec *api.PodTemplateSpec, object runtime.Object) error {
 | 
				
			||||||
 | 
						f.lock.Lock()
 | 
				
			||||||
 | 
						defer f.lock.Unlock()
 | 
				
			||||||
 | 
						if f.err != nil {
 | 
				
			||||||
 | 
							return f.err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						f.podSpec = append(f.podSpec, *spec)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (f *FakePodControl) CreatePodsOnNode(nodeName, namespace string, template *api.PodTemplateSpec, object runtime.Object) error {
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (f *FakePodControl) DeletePod(namespace string, podName string) error {
 | 
				
			||||||
 | 
						f.lock.Lock()
 | 
				
			||||||
 | 
						defer f.lock.Unlock()
 | 
				
			||||||
 | 
						if f.err != nil {
 | 
				
			||||||
 | 
							return f.err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						f.deletePodName = append(f.deletePodName, podName)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func (f *FakePodControl) clear() {
 | 
				
			||||||
 | 
						f.lock.Lock()
 | 
				
			||||||
 | 
						defer f.lock.Unlock()
 | 
				
			||||||
 | 
						f.deletePodName = []string{}
 | 
				
			||||||
 | 
						f.podSpec = []api.PodTemplateSpec{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func newJob(parallelism, completions int, restartPolicy api.RestartPolicy) *experimental.Job {
 | 
				
			||||||
 | 
						return &experimental.Job{
 | 
				
			||||||
 | 
							ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
 | 
								Name:      "foobar",
 | 
				
			||||||
 | 
								Namespace: api.NamespaceDefault,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Spec: experimental.JobSpec{
 | 
				
			||||||
 | 
								Parallelism: ¶llelism,
 | 
				
			||||||
 | 
								Completions: &completions,
 | 
				
			||||||
 | 
								Selector:    map[string]string{"foo": "bar"},
 | 
				
			||||||
 | 
								Template: &api.PodTemplateSpec{
 | 
				
			||||||
 | 
									ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
 | 
										Labels: map[string]string{
 | 
				
			||||||
 | 
											"foo": "bar",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: api.PodSpec{
 | 
				
			||||||
 | 
										RestartPolicy: restartPolicy,
 | 
				
			||||||
 | 
										Containers: []api.Container{
 | 
				
			||||||
 | 
											{Image: "foo/bar"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getKey(job *experimental.Job, t *testing.T) string {
 | 
				
			||||||
 | 
						if key, err := controller.KeyFunc(job); err != nil {
 | 
				
			||||||
 | 
							t.Errorf("Unexpected error getting key for job %v: %v", job.Name, err)
 | 
				
			||||||
 | 
							return ""
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return key
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// create count pods with the given phase for the given job
 | 
				
			||||||
 | 
					func newPodList(count int, status api.PodPhase, job *experimental.Job) []api.Pod {
 | 
				
			||||||
 | 
						pods := []api.Pod{}
 | 
				
			||||||
 | 
						for i := 0; i < count; i++ {
 | 
				
			||||||
 | 
							newPod := api.Pod{
 | 
				
			||||||
 | 
								ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
 | 
									Name:      fmt.Sprintf("pod-%v", util.Now().UnixNano()),
 | 
				
			||||||
 | 
									Labels:    job.Spec.Selector,
 | 
				
			||||||
 | 
									Namespace: job.Namespace,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Status: api.PodStatus{Phase: status},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							pods = append(pods, newPod)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return pods
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestControllerSyncJob(t *testing.T) {
 | 
				
			||||||
 | 
						testCases := map[string]struct {
 | 
				
			||||||
 | 
							// job setup
 | 
				
			||||||
 | 
							parallelism   int
 | 
				
			||||||
 | 
							completions   int
 | 
				
			||||||
 | 
							restartPolicy api.RestartPolicy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// pod setup
 | 
				
			||||||
 | 
							podControllerError error
 | 
				
			||||||
 | 
							activePods         int
 | 
				
			||||||
 | 
							successfulPods     int
 | 
				
			||||||
 | 
							unsuccessfulPods   int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// expectations
 | 
				
			||||||
 | 
							expectedCreations    int
 | 
				
			||||||
 | 
							expectedDeletions    int
 | 
				
			||||||
 | 
							expectedActive       int
 | 
				
			||||||
 | 
							expectedSuccessful   int
 | 
				
			||||||
 | 
							expectedUnsuccessful int
 | 
				
			||||||
 | 
							expectedComplete     bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							"job start": {
 | 
				
			||||||
 | 
								2, 5, api.RestartPolicyOnFailure,
 | 
				
			||||||
 | 
								nil, 0, 0, 0,
 | 
				
			||||||
 | 
								2, 0, 2, 0, 0, false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"correct # of pods": {
 | 
				
			||||||
 | 
								2, 5, api.RestartPolicyOnFailure,
 | 
				
			||||||
 | 
								nil, 2, 0, 0,
 | 
				
			||||||
 | 
								0, 0, 2, 0, 0, false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"too few active pods": {
 | 
				
			||||||
 | 
								2, 5, api.RestartPolicyOnFailure,
 | 
				
			||||||
 | 
								nil, 1, 1, 0,
 | 
				
			||||||
 | 
								1, 0, 2, 1, 0, false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"too few active pods, with controller error": {
 | 
				
			||||||
 | 
								2, 5, api.RestartPolicyOnFailure,
 | 
				
			||||||
 | 
								fmt.Errorf("Fake error"), 1, 1, 0,
 | 
				
			||||||
 | 
								0, 0, 1, 1, 0, false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"too many active pods": {
 | 
				
			||||||
 | 
								2, 5, api.RestartPolicyOnFailure,
 | 
				
			||||||
 | 
								nil, 3, 0, 0,
 | 
				
			||||||
 | 
								0, 1, 2, 0, 0, false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"too many active pods, with controller error": {
 | 
				
			||||||
 | 
								2, 5, api.RestartPolicyOnFailure,
 | 
				
			||||||
 | 
								fmt.Errorf("Fake error"), 3, 0, 0,
 | 
				
			||||||
 | 
								0, 0, 3, 0, 0, false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"failed pod and OnFailure restart policy": {
 | 
				
			||||||
 | 
								2, 5, api.RestartPolicyOnFailure,
 | 
				
			||||||
 | 
								nil, 1, 1, 1,
 | 
				
			||||||
 | 
								1, 0, 2, 1, 0, false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"failed pod and Never restart policy": {
 | 
				
			||||||
 | 
								2, 5, api.RestartPolicyNever,
 | 
				
			||||||
 | 
								nil, 1, 1, 1,
 | 
				
			||||||
 | 
								1, 0, 2, 1, 1, false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"job finish and OnFailure restart policy": {
 | 
				
			||||||
 | 
								2, 5, api.RestartPolicyOnFailure,
 | 
				
			||||||
 | 
								nil, 0, 5, 0,
 | 
				
			||||||
 | 
								0, 0, 0, 5, 0, true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"job finish and Never restart policy": {
 | 
				
			||||||
 | 
								2, 5, api.RestartPolicyNever,
 | 
				
			||||||
 | 
								nil, 0, 2, 3,
 | 
				
			||||||
 | 
								0, 0, 0, 2, 3, true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"more active pods than completions": {
 | 
				
			||||||
 | 
								2, 5, api.RestartPolicyOnFailure,
 | 
				
			||||||
 | 
								nil, 10, 0, 0,
 | 
				
			||||||
 | 
								0, 8, 2, 0, 0, false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"status change": {
 | 
				
			||||||
 | 
								2, 5, api.RestartPolicyOnFailure,
 | 
				
			||||||
 | 
								nil, 2, 2, 0,
 | 
				
			||||||
 | 
								0, 0, 2, 2, 0, false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for name, tc := range testCases {
 | 
				
			||||||
 | 
							// job manager setup
 | 
				
			||||||
 | 
							client := client.NewOrDie(&client.Config{Host: "", Version: testapi.Experimental.Version()})
 | 
				
			||||||
 | 
							manager := NewJobManager(client)
 | 
				
			||||||
 | 
							fakePodControl := FakePodControl{err: tc.podControllerError}
 | 
				
			||||||
 | 
							manager.podControl = &fakePodControl
 | 
				
			||||||
 | 
							manager.podStoreSynced = alwaysReady
 | 
				
			||||||
 | 
							var actual *experimental.Job
 | 
				
			||||||
 | 
							manager.updateHandler = func(job *experimental.Job) error {
 | 
				
			||||||
 | 
								actual = job
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// job & pods setup
 | 
				
			||||||
 | 
							job := newJob(tc.parallelism, tc.completions, tc.restartPolicy)
 | 
				
			||||||
 | 
							manager.jobStore.Store.Add(job)
 | 
				
			||||||
 | 
							for _, pod := range newPodList(tc.activePods, api.PodRunning, job) {
 | 
				
			||||||
 | 
								manager.podStore.Store.Add(&pod)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, pod := range newPodList(tc.successfulPods, api.PodSucceeded, job) {
 | 
				
			||||||
 | 
								manager.podStore.Store.Add(&pod)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, pod := range newPodList(tc.unsuccessfulPods, api.PodFailed, job) {
 | 
				
			||||||
 | 
								manager.podStore.Store.Add(&pod)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// run
 | 
				
			||||||
 | 
							err := manager.syncJob(getKey(job, t))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Errorf("%s: unexpected error when syncing jobs %v", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// validate created/deleted pods
 | 
				
			||||||
 | 
							if len(fakePodControl.podSpec) != tc.expectedCreations {
 | 
				
			||||||
 | 
								t.Errorf("%s: unexpected number of creates.  Expected %d, saw %d\n", name, tc.expectedCreations, len(fakePodControl.podSpec))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(fakePodControl.deletePodName) != tc.expectedDeletions {
 | 
				
			||||||
 | 
								t.Errorf("%s: unexpected number of deletes.  Expected %d, saw %d\n", name, tc.expectedDeletions, len(fakePodControl.deletePodName))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// validate status
 | 
				
			||||||
 | 
							if actual.Status.Active != tc.expectedActive {
 | 
				
			||||||
 | 
								t.Errorf("%s: unexpected number of active pods.  Expected %d, saw %d\n", name, tc.expectedActive, actual.Status.Active)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if actual.Status.Successful != tc.expectedSuccessful {
 | 
				
			||||||
 | 
								t.Errorf("%s: unexpected number of successful pods.  Expected %d, saw %d\n", name, tc.expectedSuccessful, actual.Status.Successful)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if actual.Status.Unsuccessful != tc.expectedUnsuccessful {
 | 
				
			||||||
 | 
								t.Errorf("%s: unexpected number of unsuccessful pods.  Expected %d, saw %d\n", name, tc.expectedUnsuccessful, actual.Status.Unsuccessful)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// validate conditions
 | 
				
			||||||
 | 
							if tc.expectedComplete {
 | 
				
			||||||
 | 
								completed := false
 | 
				
			||||||
 | 
								for _, v := range actual.Status.Conditions {
 | 
				
			||||||
 | 
									if v.Type == experimental.JobComplete && v.Status == api.ConditionTrue {
 | 
				
			||||||
 | 
										completed = true
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !completed {
 | 
				
			||||||
 | 
									t.Errorf("%s: expected completion condition.  Got %v", name, actual.Status.Conditions)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestSyncJobDeleted(t *testing.T) {
 | 
				
			||||||
 | 
						client := client.NewOrDie(&client.Config{Host: "", Version: testapi.Experimental.Version()})
 | 
				
			||||||
 | 
						manager := NewJobManager(client)
 | 
				
			||||||
 | 
						fakePodControl := FakePodControl{}
 | 
				
			||||||
 | 
						manager.podControl = &fakePodControl
 | 
				
			||||||
 | 
						manager.podStoreSynced = alwaysReady
 | 
				
			||||||
 | 
						manager.updateHandler = func(job *experimental.Job) error { return nil }
 | 
				
			||||||
 | 
						job := newJob(2, 2, api.RestartPolicyOnFailure)
 | 
				
			||||||
 | 
						err := manager.syncJob(getKey(job, t))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Errorf("Unexpected error when syncing jobs %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(fakePodControl.podSpec) != 0 {
 | 
				
			||||||
 | 
							t.Errorf("Unexpected number of creates.  Expected %d, saw %d\n", 0, len(fakePodControl.podSpec))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(fakePodControl.deletePodName) != 0 {
 | 
				
			||||||
 | 
							t.Errorf("Unexpected number of deletes.  Expected %d, saw %d\n", 0, len(fakePodControl.deletePodName))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestSyncJobUpdateRequeue(t *testing.T) {
 | 
				
			||||||
 | 
						client := client.NewOrDie(&client.Config{Host: "", Version: testapi.Experimental.Version()})
 | 
				
			||||||
 | 
						manager := NewJobManager(client)
 | 
				
			||||||
 | 
						fakePodControl := FakePodControl{}
 | 
				
			||||||
 | 
						manager.podControl = &fakePodControl
 | 
				
			||||||
 | 
						manager.podStoreSynced = alwaysReady
 | 
				
			||||||
 | 
						manager.updateHandler = func(job *experimental.Job) error { return fmt.Errorf("Fake error") }
 | 
				
			||||||
 | 
						job := newJob(2, 2, api.RestartPolicyOnFailure)
 | 
				
			||||||
 | 
						manager.jobStore.Store.Add(job)
 | 
				
			||||||
 | 
						err := manager.syncJob(getKey(job, t))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Errorf("Unxpected error when syncing jobs, got %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ch := make(chan interface{})
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							item, _ := manager.queue.Get()
 | 
				
			||||||
 | 
							ch <- item
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case key := <-ch:
 | 
				
			||||||
 | 
							expectedKey := getKey(job, t)
 | 
				
			||||||
 | 
							if key != expectedKey {
 | 
				
			||||||
 | 
								t.Errorf("Expected requeue of job with key %s got %s", expectedKey, key)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case <-time.After(controllerTimeout):
 | 
				
			||||||
 | 
							manager.queue.ShutDown()
 | 
				
			||||||
 | 
							t.Errorf("Expected to find a job in the queue, found none.")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestJobPodLookup(t *testing.T) {
 | 
				
			||||||
 | 
						client := client.NewOrDie(&client.Config{Host: "", Version: testapi.Experimental.Version()})
 | 
				
			||||||
 | 
						manager := NewJobManager(client)
 | 
				
			||||||
 | 
						manager.podStoreSynced = alwaysReady
 | 
				
			||||||
 | 
						testCases := []struct {
 | 
				
			||||||
 | 
							job *experimental.Job
 | 
				
			||||||
 | 
							pod *api.Pod
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expectedName string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							// pods without labels don't match any job
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								job: &experimental.Job{
 | 
				
			||||||
 | 
									ObjectMeta: api.ObjectMeta{Name: "basic"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								pod: &api.Pod{
 | 
				
			||||||
 | 
									ObjectMeta: api.ObjectMeta{Name: "foo1", Namespace: api.NamespaceAll},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectedName: "",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							// matching labels, different namespace
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								job: &experimental.Job{
 | 
				
			||||||
 | 
									ObjectMeta: api.ObjectMeta{Name: "foo"},
 | 
				
			||||||
 | 
									Spec: experimental.JobSpec{
 | 
				
			||||||
 | 
										Selector: map[string]string{"foo": "bar"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								pod: &api.Pod{
 | 
				
			||||||
 | 
									ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
 | 
										Name:      "foo2",
 | 
				
			||||||
 | 
										Namespace: "ns",
 | 
				
			||||||
 | 
										Labels:    map[string]string{"foo": "bar"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectedName: "",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							// matching ns and labels returns
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								job: &experimental.Job{
 | 
				
			||||||
 | 
									ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "ns"},
 | 
				
			||||||
 | 
									Spec: experimental.JobSpec{
 | 
				
			||||||
 | 
										Selector: map[string]string{"foo": "bar"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								pod: &api.Pod{
 | 
				
			||||||
 | 
									ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
 | 
										Name:      "foo3",
 | 
				
			||||||
 | 
										Namespace: "ns",
 | 
				
			||||||
 | 
										Labels:    map[string]string{"foo": "bar"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expectedName: "bar",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tc := range testCases {
 | 
				
			||||||
 | 
							manager.jobStore.Add(tc.job)
 | 
				
			||||||
 | 
							if job := manager.getPodJob(tc.pod); job != nil {
 | 
				
			||||||
 | 
								if tc.expectedName != job.Name {
 | 
				
			||||||
 | 
									t.Errorf("Got job %+v expected %+v", job.Name, tc.expectedName)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else if tc.expectedName != "" {
 | 
				
			||||||
 | 
								t.Errorf("Expected a job %v pod %v, found none", tc.expectedName, tc.pod.Name)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FakeJobExpectations struct {
 | 
				
			||||||
 | 
						*controller.ControllerExpectations
 | 
				
			||||||
 | 
						satisfied    bool
 | 
				
			||||||
 | 
						expSatisfied func()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (fe FakeJobExpectations) SatisfiedExpectations(controllerKey string) bool {
 | 
				
			||||||
 | 
						fe.expSatisfied()
 | 
				
			||||||
 | 
						return fe.satisfied
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestSyncJobExpectations tests that a pod cannot sneak in between counting active pods
 | 
				
			||||||
 | 
					// and checking expectations.
 | 
				
			||||||
 | 
					func TestSyncJobExpectations(t *testing.T) {
 | 
				
			||||||
 | 
						client := client.NewOrDie(&client.Config{Host: "", Version: testapi.Experimental.Version()})
 | 
				
			||||||
 | 
						manager := NewJobManager(client)
 | 
				
			||||||
 | 
						fakePodControl := FakePodControl{}
 | 
				
			||||||
 | 
						manager.podControl = &fakePodControl
 | 
				
			||||||
 | 
						manager.podStoreSynced = alwaysReady
 | 
				
			||||||
 | 
						manager.updateHandler = func(job *experimental.Job) error { return nil }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						job := newJob(2, 2, api.RestartPolicyOnFailure)
 | 
				
			||||||
 | 
						manager.jobStore.Store.Add(job)
 | 
				
			||||||
 | 
						pods := newPodList(2, api.PodPending, job)
 | 
				
			||||||
 | 
						manager.podStore.Store.Add(&pods[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						manager.expectations = FakeJobExpectations{
 | 
				
			||||||
 | 
							controller.NewControllerExpectations(), true, func() {
 | 
				
			||||||
 | 
								// If we check active pods before checking expectataions, the job
 | 
				
			||||||
 | 
								// will create a new replica because it doesn't see this pod, but
 | 
				
			||||||
 | 
								// has fulfilled its expectations.
 | 
				
			||||||
 | 
								manager.podStore.Store.Add(&pods[1])
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						manager.syncJob(getKey(job, t))
 | 
				
			||||||
 | 
						if len(fakePodControl.podSpec) != 0 {
 | 
				
			||||||
 | 
							t.Errorf("Unexpected number of creates.  Expected %d, saw %d\n", 0, len(fakePodControl.podSpec))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(fakePodControl.deletePodName) != 0 {
 | 
				
			||||||
 | 
							t.Errorf("Unexpected number of deletes.  Expected %d, saw %d\n", 0, len(fakePodControl.deletePodName))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FakeWatcher struct {
 | 
				
			||||||
 | 
						w *watch.FakeWatcher
 | 
				
			||||||
 | 
						*testclient.Fake
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestWatchJobs(t *testing.T) {
 | 
				
			||||||
 | 
						fakeWatch := watch.NewFake()
 | 
				
			||||||
 | 
						client := &testclient.Fake{}
 | 
				
			||||||
 | 
						client.AddWatchReactor("*", testclient.DefaultWatchReactor(fakeWatch, nil))
 | 
				
			||||||
 | 
						manager := NewJobManager(client)
 | 
				
			||||||
 | 
						manager.podStoreSynced = alwaysReady
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var testJob experimental.Job
 | 
				
			||||||
 | 
						received := make(chan string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The update sent through the fakeWatcher should make its way into the workqueue,
 | 
				
			||||||
 | 
						// and eventually into the syncHandler.
 | 
				
			||||||
 | 
						manager.syncHandler = func(key string) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							obj, exists, err := manager.jobStore.Store.GetByKey(key)
 | 
				
			||||||
 | 
							if !exists || err != nil {
 | 
				
			||||||
 | 
								t.Errorf("Expected to find job under key %v", key)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							job := *obj.(*experimental.Job)
 | 
				
			||||||
 | 
							if !api.Semantic.DeepDerivative(job, testJob) {
 | 
				
			||||||
 | 
								t.Errorf("Expected %#v, but got %#v", testJob, job)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							received <- key
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Start only the job watcher and the workqueue, send a watch event,
 | 
				
			||||||
 | 
						// and make sure it hits the sync method.
 | 
				
			||||||
 | 
						stopCh := make(chan struct{})
 | 
				
			||||||
 | 
						defer close(stopCh)
 | 
				
			||||||
 | 
						go manager.jobController.Run(stopCh)
 | 
				
			||||||
 | 
						go util.Until(manager.worker, 10*time.Millisecond, stopCh)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// We're sending new job to see if it reaches syncHandler.
 | 
				
			||||||
 | 
						testJob.Name = "foo"
 | 
				
			||||||
 | 
						fakeWatch.Add(&testJob)
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-received:
 | 
				
			||||||
 | 
						case <-time.After(controllerTimeout):
 | 
				
			||||||
 | 
							t.Errorf("Expected 1 call but got 0")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// We're sending fake finished job, to see if it reaches syncHandler - it should not,
 | 
				
			||||||
 | 
						// since we're filtering out finished jobs.
 | 
				
			||||||
 | 
						testJobv2 := experimental.Job{
 | 
				
			||||||
 | 
							ObjectMeta: api.ObjectMeta{Name: "foo"},
 | 
				
			||||||
 | 
							Status: experimental.JobStatus{
 | 
				
			||||||
 | 
								Conditions: []experimental.JobCondition{{
 | 
				
			||||||
 | 
									Type:               experimental.JobComplete,
 | 
				
			||||||
 | 
									Status:             api.ConditionTrue,
 | 
				
			||||||
 | 
									LastProbeTime:      util.Now(),
 | 
				
			||||||
 | 
									LastTransitionTime: util.Now(),
 | 
				
			||||||
 | 
								}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						fakeWatch.Modify(&testJobv2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-received:
 | 
				
			||||||
 | 
							t.Errorf("Expected 0 call but got 1")
 | 
				
			||||||
 | 
						case <-time.After(controllerTimeout):
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestWatchPods(t *testing.T) {
 | 
				
			||||||
 | 
						fakeWatch := watch.NewFake()
 | 
				
			||||||
 | 
						client := &testclient.Fake{}
 | 
				
			||||||
 | 
						client.AddWatchReactor("*", testclient.DefaultWatchReactor(fakeWatch, nil))
 | 
				
			||||||
 | 
						manager := NewJobManager(client)
 | 
				
			||||||
 | 
						manager.podStoreSynced = alwaysReady
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Put one job and one pod into the store
 | 
				
			||||||
 | 
						testJob := newJob(2, 2, api.RestartPolicyOnFailure)
 | 
				
			||||||
 | 
						manager.jobStore.Store.Add(testJob)
 | 
				
			||||||
 | 
						received := make(chan string)
 | 
				
			||||||
 | 
						// The pod update sent through the fakeWatcher should figure out the managing job and
 | 
				
			||||||
 | 
						// send it into the syncHandler.
 | 
				
			||||||
 | 
						manager.syncHandler = func(key string) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							obj, exists, err := manager.jobStore.Store.GetByKey(key)
 | 
				
			||||||
 | 
							if !exists || err != nil {
 | 
				
			||||||
 | 
								t.Errorf("Expected to find job under key %v", key)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							job := obj.(*experimental.Job)
 | 
				
			||||||
 | 
							if !api.Semantic.DeepDerivative(job, testJob) {
 | 
				
			||||||
 | 
								t.Errorf("\nExpected %#v,\nbut got %#v", testJob, job)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							close(received)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Start only the pod watcher and the workqueue, send a watch event,
 | 
				
			||||||
 | 
						// and make sure it hits the sync method for the right job.
 | 
				
			||||||
 | 
						stopCh := make(chan struct{})
 | 
				
			||||||
 | 
						defer close(stopCh)
 | 
				
			||||||
 | 
						go manager.podController.Run(stopCh)
 | 
				
			||||||
 | 
						go util.Until(manager.worker, 10*time.Millisecond, stopCh)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pods := newPodList(1, api.PodRunning, testJob)
 | 
				
			||||||
 | 
						testPod := pods[0]
 | 
				
			||||||
 | 
						testPod.Status.Phase = api.PodFailed
 | 
				
			||||||
 | 
						fakeWatch.Add(&testPod)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-received:
 | 
				
			||||||
 | 
						case <-time.After(controllerTimeout):
 | 
				
			||||||
 | 
							t.Errorf("Expected 1 call but got 0")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -350,7 +350,7 @@ func (rm *ReplicationManager) manageReplicas(filteredPods []*api.Pod, rc *api.Re
 | 
				
			|||||||
		for i := 0; i < diff; i++ {
 | 
							for i := 0; i < diff; i++ {
 | 
				
			||||||
			go func() {
 | 
								go func() {
 | 
				
			||||||
				defer wait.Done()
 | 
									defer wait.Done()
 | 
				
			||||||
				if err := rm.podControl.CreateReplica(rc.Namespace, rc); err != nil {
 | 
									if err := rm.podControl.CreatePods(rc.Namespace, rc.Spec.Template, rc); err != nil {
 | 
				
			||||||
					// Decrement the expected number of creates because the informer won't observe this pod
 | 
										// Decrement the expected number of creates because the informer won't observe this pod
 | 
				
			||||||
					glog.V(2).Infof("Failed creation, decrementing expectations for controller %q/%q", rc.Namespace, rc.Name)
 | 
										glog.V(2).Infof("Failed creation, decrementing expectations for controller %q/%q", rc.Namespace, rc.Name)
 | 
				
			||||||
					rm.expectations.CreationObserved(rcKey)
 | 
										rm.expectations.CreationObserved(rcKey)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,6 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/api"
 | 
						"k8s.io/kubernetes/pkg/api"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/api/testapi"
 | 
						"k8s.io/kubernetes/pkg/api/testapi"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/apis/experimental"
 | 
					 | 
				
			||||||
	"k8s.io/kubernetes/pkg/client/cache"
 | 
						"k8s.io/kubernetes/pkg/client/cache"
 | 
				
			||||||
	client "k8s.io/kubernetes/pkg/client/unversioned"
 | 
						client "k8s.io/kubernetes/pkg/client/unversioned"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/client/unversioned/testclient"
 | 
						"k8s.io/kubernetes/pkg/client/unversioned/testclient"
 | 
				
			||||||
@@ -42,7 +41,7 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type FakePodControl struct {
 | 
					type FakePodControl struct {
 | 
				
			||||||
	controllerSpec []api.ReplicationController
 | 
						controllerSpec []api.PodTemplateSpec
 | 
				
			||||||
	deletePodName  []string
 | 
						deletePodName  []string
 | 
				
			||||||
	lock           sync.Mutex
 | 
						lock           sync.Mutex
 | 
				
			||||||
	err            error
 | 
						err            error
 | 
				
			||||||
@@ -60,7 +59,7 @@ func init() {
 | 
				
			|||||||
	api.ForTesting_ReferencesAllowBlankSelfLinks = true
 | 
						api.ForTesting_ReferencesAllowBlankSelfLinks = true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (f *FakePodControl) CreateReplica(namespace string, spec *api.ReplicationController) error {
 | 
					func (f *FakePodControl) CreatePods(namespace string, spec *api.PodTemplateSpec, object runtime.Object) error {
 | 
				
			||||||
	f.lock.Lock()
 | 
						f.lock.Lock()
 | 
				
			||||||
	defer f.lock.Unlock()
 | 
						defer f.lock.Unlock()
 | 
				
			||||||
	if f.err != nil {
 | 
						if f.err != nil {
 | 
				
			||||||
@@ -70,7 +69,7 @@ func (f *FakePodControl) CreateReplica(namespace string, spec *api.ReplicationCo
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (f *FakePodControl) CreateReplicaOnNode(namespace string, daemon *experimental.DaemonSet, nodeName string) error {
 | 
					func (f *FakePodControl) CreatePodsOnNode(nodeName, namespace string, template *api.PodTemplateSpec, object runtime.Object) error {
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -87,7 +86,7 @@ func (f *FakePodControl) clear() {
 | 
				
			|||||||
	f.lock.Lock()
 | 
						f.lock.Lock()
 | 
				
			||||||
	defer f.lock.Unlock()
 | 
						defer f.lock.Unlock()
 | 
				
			||||||
	f.deletePodName = []string{}
 | 
						f.deletePodName = []string{}
 | 
				
			||||||
	f.controllerSpec = []api.ReplicationController{}
 | 
						f.controllerSpec = []api.PodTemplateSpec{}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getKey(rc *api.ReplicationController, t *testing.T) string {
 | 
					func getKey(rc *api.ReplicationController, t *testing.T) string {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user