#29 Restart Jenkins pod when any seed job has been deleted

This commit is contained in:
Tomasz Sęk 2019-06-29 12:19:35 +02:00
parent 3b26e1c5ba
commit dda0a0075e
No known key found for this signature in database
GPG Key ID: DC356D23F6A644D0
5 changed files with 142 additions and 12 deletions

View File

@ -334,6 +334,10 @@ type JenkinsStatus struct {
// UserAndPasswordHash is a SHA256 hash made from user and password // UserAndPasswordHash is a SHA256 hash made from user and password
// +optional // +optional
UserAndPasswordHash string `json:"userAndPasswordHash,omitempty"` UserAndPasswordHash string `json:"userAndPasswordHash,omitempty"`
// CreatedSeedJobs contains list of seed job id already created in Jenkins
// +optional
CreatedSeedJobs []string `json:"createdSeedJobs,omitempty"`
} }
// BuildStatus defines type of Jenkins build job status // BuildStatus defines type of Jenkins build job status

View File

@ -121,7 +121,7 @@ func (r *ReconcileJenkinsBaseConfiguration) Reconcile() (reconcile.Result, jenki
} }
if !ok { if !ok {
r.logger.Info("Some plugins have changed, restarting Jenkins") r.logger.Info("Some plugins have changed, restarting Jenkins")
return reconcile.Result{Requeue: true}, nil, r.restartJenkinsMasterPod(metaObject) return reconcile.Result{Requeue: true}, nil, r.restartJenkinsMasterPod()
} }
result, err = r.ensureBaseConfiguration(jenkinsClient) result, err = r.ensureBaseConfiguration(jenkinsClient)
@ -368,7 +368,7 @@ func (r *ReconcileJenkinsBaseConfiguration) createService(meta metav1.ObjectMeta
return stackerr.WithStack(r.updateResource(&service)) return stackerr.WithStack(r.updateResource(&service))
} }
func (r *ReconcileJenkinsBaseConfiguration) getJenkinsMasterPod(meta metav1.ObjectMeta) (*corev1.Pod, error) { func (r *ReconcileJenkinsBaseConfiguration) getJenkinsMasterPod() (*corev1.Pod, error) {
jenkinsMasterPodName := resources.GetJenkinsMasterPodName(*r.jenkins) jenkinsMasterPodName := resources.GetJenkinsMasterPodName(*r.jenkins)
currentJenkinsMasterPod := &corev1.Pod{} currentJenkinsMasterPod := &corev1.Pod{}
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: jenkinsMasterPodName, Namespace: r.jenkins.Namespace}, currentJenkinsMasterPod) err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: jenkinsMasterPodName, Namespace: r.jenkins.Namespace}, currentJenkinsMasterPod)
@ -385,7 +385,7 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureJenkinsMasterPod(meta metav1.O
} }
// Check if this Pod already exists // Check if this Pod already exists
currentJenkinsMasterPod, err := r.getJenkinsMasterPod(meta) currentJenkinsMasterPod, err := r.getJenkinsMasterPod()
if err != nil && errors.IsNotFound(err) { if err != nil && errors.IsNotFound(err) {
jenkinsMasterPod := resources.NewJenkinsMasterPod(meta, r.jenkins) jenkinsMasterPod := resources.NewJenkinsMasterPod(meta, r.jenkins)
if !reflect.DeepEqual(jenkinsMasterPod.Spec.Containers[0].Command, resources.GetJenkinsMasterContainerBaseCommand()) { if !reflect.DeepEqual(jenkinsMasterPod.Spec.Containers[0].Command, resources.GetJenkinsMasterContainerBaseCommand()) {
@ -433,7 +433,7 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureJenkinsMasterPod(meta metav1.O
return reconcile.Result{Requeue: true}, nil return reconcile.Result{Requeue: true}, nil
} }
if currentJenkinsMasterPod != nil && r.isRecreatePodNeeded(*currentJenkinsMasterPod, userAndPasswordHash) { if currentJenkinsMasterPod != nil && r.isRecreatePodNeeded(*currentJenkinsMasterPod, userAndPasswordHash) {
return reconcile.Result{Requeue: true}, r.restartJenkinsMasterPod(meta) return reconcile.Result{Requeue: true}, r.restartJenkinsMasterPod()
} }
return reconcile.Result{}, nil return reconcile.Result{}, nil
@ -638,8 +638,8 @@ func (r *ReconcileJenkinsBaseConfiguration) compareVolumes(actualPod corev1.Pod)
) )
} }
func (r *ReconcileJenkinsBaseConfiguration) restartJenkinsMasterPod(meta metav1.ObjectMeta) error { func (r *ReconcileJenkinsBaseConfiguration) restartJenkinsMasterPod() error {
currentJenkinsMasterPod, err := r.getJenkinsMasterPod(meta) currentJenkinsMasterPod, err := r.getJenkinsMasterPod()
if err != nil { if err != nil {
return err return err
} }
@ -648,7 +648,7 @@ func (r *ReconcileJenkinsBaseConfiguration) restartJenkinsMasterPod(meta metav1.
} }
func (r *ReconcileJenkinsBaseConfiguration) detectJenkinsMasterPodStartingIssues(meta metav1.ObjectMeta) (stopReconcileLoop bool, err error) { func (r *ReconcileJenkinsBaseConfiguration) detectJenkinsMasterPodStartingIssues(meta metav1.ObjectMeta) (stopReconcileLoop bool, err error) {
jenkinsMasterPod, err := r.getJenkinsMasterPod(meta) jenkinsMasterPod, err := r.getJenkinsMasterPod()
if err != nil { if err != nil {
return false, err return false, err
} }
@ -696,7 +696,7 @@ func (r *ReconcileJenkinsBaseConfiguration) filterEvents(source corev1.EventList
} }
func (r *ReconcileJenkinsBaseConfiguration) waitForJenkins(meta metav1.ObjectMeta) (reconcile.Result, error) { func (r *ReconcileJenkinsBaseConfiguration) waitForJenkins(meta metav1.ObjectMeta) (reconcile.Result, error) {
jenkinsMasterPod, err := r.getJenkinsMasterPod(meta) jenkinsMasterPod, err := r.getJenkinsMasterPod()
if err != nil { if err != nil {
return reconcile.Result{}, err return reconcile.Result{}, err
} }
@ -715,7 +715,7 @@ func (r *ReconcileJenkinsBaseConfiguration) waitForJenkins(meta metav1.ObjectMet
for _, containerStatus := range jenkinsMasterPod.Status.ContainerStatuses { for _, containerStatus := range jenkinsMasterPod.Status.ContainerStatuses {
if containerStatus.State.Terminated != nil { if containerStatus.State.Terminated != nil {
r.logger.Info(fmt.Sprintf("Container '%s' is terminated, status '%+v', recreating pod", containerStatus.Name, containerStatus)) r.logger.Info(fmt.Sprintf("Container '%s' is terminated, status '%+v', recreating pod", containerStatus.Name, containerStatus))
return reconcile.Result{Requeue: true}, r.restartJenkinsMasterPod(meta) return reconcile.Result{Requeue: true}, r.restartJenkinsMasterPod()
} }
if !containerStatus.Ready { if !containerStatus.Ready {
r.logger.V(log.VDebug).Info(fmt.Sprintf("Container '%s' not ready, readiness probe failed", containerStatus.Name)) r.logger.V(log.VDebug).Info(fmt.Sprintf("Container '%s' not ready, readiness probe failed", containerStatus.Name))
@ -743,7 +743,7 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureJenkinsClient(meta metav1.Obje
if err != nil { if err != nil {
return nil, stackerr.WithStack(err) return nil, stackerr.WithStack(err)
} }
currentJenkinsMasterPod, err := r.getJenkinsMasterPod(meta) currentJenkinsMasterPod, err := r.getJenkinsMasterPod()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -98,7 +98,7 @@ func (r *ReconcileUserConfiguration) ensureSeedJobs() (reconcile.Result, error)
} }
// build not finished yet - requeue reconciliation loop with timeout // build not finished yet - requeue reconciliation loop with timeout
if !done { if !done {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 10}, nil return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil
} }
return reconcile.Result{}, nil return reconcile.Result{}, nil
} }

View File

@ -5,6 +5,7 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"reflect"
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client"
@ -61,6 +62,11 @@ func New(jenkinsClient jenkinsclient.Jenkins, k8sClient k8s.Client, logger logr.
// EnsureSeedJobs configures seed job and runs it for every entry from Jenkins.Spec.SeedJobs // EnsureSeedJobs configures seed job and runs it for every entry from Jenkins.Spec.SeedJobs
func (s *SeedJobs) EnsureSeedJobs(jenkins *v1alpha2.Jenkins) (done bool, err error) { func (s *SeedJobs) EnsureSeedJobs(jenkins *v1alpha2.Jenkins) (done bool, err error) {
if s.isRecreatePodNeeded(*jenkins) {
s.logger.Info("Some seed job has been deleted, recreating pod")
return false, s.restartJenkinsMasterPod(*jenkins)
}
if err = s.createJob(); err != nil { if err = s.createJob(); err != nil {
s.logger.V(log.VWarn).Info("Couldn't create jenkins seed job") s.logger.V(log.VWarn).Info("Couldn't create jenkins seed job")
return false, err return false, err
@ -75,6 +81,13 @@ func (s *SeedJobs) EnsureSeedJobs(jenkins *v1alpha2.Jenkins) (done bool, err err
s.logger.V(log.VWarn).Info("Couldn't build jenkins seed job") s.logger.V(log.VWarn).Info("Couldn't build jenkins seed job")
return false, err return false, err
} }
seedJobIDs := s.getAllSeedJobIDs(*jenkins)
if done && !reflect.DeepEqual(seedJobIDs, jenkins.Status.CreatedSeedJobs) {
jenkins.Status.CreatedSeedJobs = seedJobIDs
return false, stackerr.WithStack(s.k8sClient.Update(context.TODO(), jenkins))
}
return done, nil return done, nil
} }
@ -175,6 +188,51 @@ func (s *SeedJobs) credentialValue(namespace string, seedJob v1alpha2.SeedJob) (
return "", nil return "", nil
} }
func (s *SeedJobs) getAllSeedJobIDs(jenkins v1alpha2.Jenkins) []string {
var ids []string
for _, seedJob := range jenkins.Spec.SeedJobs {
ids = append(ids, seedJob.ID)
}
return ids
}
//TODO move to k8sClient
func (s *SeedJobs) getJenkinsMasterPod(jenkins v1alpha2.Jenkins) (*corev1.Pod, error) {
jenkinsMasterPodName := resources.GetJenkinsMasterPodName(jenkins)
currentJenkinsMasterPod := &corev1.Pod{}
err := s.k8sClient.Get(context.TODO(), types.NamespacedName{Name: jenkinsMasterPodName, Namespace: jenkins.Namespace}, currentJenkinsMasterPod)
if err != nil {
return nil, err // don't wrap error
}
return currentJenkinsMasterPod, nil
}
//TODO move to k8sClient
func (s *SeedJobs) restartJenkinsMasterPod(jenkins v1alpha2.Jenkins) error {
currentJenkinsMasterPod, err := s.getJenkinsMasterPod(jenkins)
if err != nil {
return err
}
s.logger.Info(fmt.Sprintf("Terminating Jenkins Master Pod %s/%s", currentJenkinsMasterPod.Namespace, currentJenkinsMasterPod.Name))
return stackerr.WithStack(s.k8sClient.Delete(context.TODO(), currentJenkinsMasterPod))
}
func (s *SeedJobs) isRecreatePodNeeded(jenkins v1alpha2.Jenkins) bool {
for _, createdSeedJob := range jenkins.Status.CreatedSeedJobs {
found := false
for _, seedJob := range jenkins.Spec.SeedJobs {
if createdSeedJob == seedJob.ID {
found = true
break
}
}
if !found {
return true
}
}
return false
}
// seedJobConfigXML this is the XML representation of seed job // seedJobConfigXML this is the XML representation of seed job
var seedJobConfigXML = ` var seedJobConfigXML = `
<flow-definition plugin="workflow-job@2.30"> <flow-definition plugin="workflow-job@2.30">

View File

@ -105,7 +105,7 @@ func TestEnsureSeedJobs(t *testing.T) {
// second run - should update and finish job // second run - should update and finish job
if reconcileAttempt == 2 { if reconcileAttempt == 2 {
assert.True(t, done) assert.False(t, done)
assert.Equal(t, string(v1alpha2.BuildSuccessStatus), string(build.Status)) assert.Equal(t, string(v1alpha2.BuildSuccessStatus), string(build.Status))
} }
@ -151,3 +151,71 @@ func jenkinsCustomResource() *v1alpha2.Jenkins {
}, },
} }
} }
func TestSeedJobs_isRecreatePodNeeded(t *testing.T) {
seedJobsClient := New(nil, nil, nil)
t.Run("empty", func(t *testing.T) {
jenkins := v1alpha2.Jenkins{}
got := seedJobsClient.isRecreatePodNeeded(jenkins)
assert.False(t, got)
})
t.Run("same", func(t *testing.T) {
jenkins := v1alpha2.Jenkins{
Spec: v1alpha2.JenkinsSpec{
SeedJobs: []v1alpha2.SeedJob{
{
ID: "name",
},
},
},
Status: v1alpha2.JenkinsStatus{
CreatedSeedJobs: []string{"name"},
},
}
got := seedJobsClient.isRecreatePodNeeded(jenkins)
assert.False(t, got)
})
t.Run("removed one", func(t *testing.T) {
jenkins := v1alpha2.Jenkins{
Spec: v1alpha2.JenkinsSpec{
SeedJobs: []v1alpha2.SeedJob{
{
ID: "name1",
},
},
},
Status: v1alpha2.JenkinsStatus{
CreatedSeedJobs: []string{"name1", "name2"},
},
}
got := seedJobsClient.isRecreatePodNeeded(jenkins)
assert.True(t, got)
})
t.Run("renamed one", func(t *testing.T) {
jenkins := v1alpha2.Jenkins{
Spec: v1alpha2.JenkinsSpec{
SeedJobs: []v1alpha2.SeedJob{
{
ID: "name1",
},
{
ID: "name3",
},
},
},
Status: v1alpha2.JenkinsStatus{
CreatedSeedJobs: []string{"name1", "name2"},
},
}
got := seedJobsClient.isRecreatePodNeeded(jenkins)
assert.True(t, got)
})
}