Skip to content

Commit ac25caa

Browse files
committed
Raise error in Plan handler if drain options conflict
Signed-off-by: Brad Davidson <[email protected]>
1 parent 0c3aa94 commit ac25caa

File tree

4 files changed

+85
-0
lines changed

4 files changed

+85
-0
lines changed

e2e/suite/job_generate_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"time"
55

66
batchv1 "k8s.io/api/batch/v1"
7+
"k8s.io/utils/pointer"
78

89
"github.com/rancher/system-upgrade-controller/e2e/framework"
910
upgradeapiv1 "github.com/rancher/system-upgrade-controller/pkg/apis/upgrade.cattle.io/v1"
@@ -59,4 +60,61 @@ var _ = Describe("Job Generation", func() {
5960
Expect(jobs[0].Status.Failed).To(BeNumerically("==", 0))
6061
})
6162
})
63+
64+
When("fails because of conflicting drain options", func() {
65+
var (
66+
err error
67+
plan *upgradeapiv1.Plan
68+
jobs []batchv1.Job
69+
)
70+
BeforeEach(func() {
71+
plan = e2e.NewPlan("fail-drain-options-", "library/alpine:3.11", []string{"sh", "-c"}, "exit 0")
72+
plan.Spec.Version = "latest"
73+
plan.Spec.Concurrency = 1
74+
plan.Spec.NodeSelector = &metav1.LabelSelector{
75+
MatchExpressions: []metav1.LabelSelectorRequirement{{
76+
Key: "node-role.kubernetes.io/master",
77+
Operator: metav1.LabelSelectorOpDoesNotExist,
78+
}},
79+
}
80+
plan.Spec.Drain = &upgradeapiv1.DrainSpec{
81+
DisableEviction: true,
82+
DeleteLocalData: pointer.Bool(true),
83+
DeleteEmptydirData: pointer.Bool(true),
84+
PodSelector: &metav1.LabelSelector{
85+
MatchExpressions: []metav1.LabelSelectorRequirement{{
86+
Key: "app",
87+
Values: []string{"csi-attacher", "csi-provisioner"},
88+
Operator: metav1.LabelSelectorOpNotIn,
89+
}},
90+
},
91+
}
92+
plan, err = e2e.CreatePlan(plan)
93+
Expect(err).ToNot(HaveOccurred())
94+
95+
plan, err = e2e.WaitForPlanCondition(plan.Name, upgradeapiv1.PlanSpecValidated, 30*time.Second)
96+
Expect(err).ToNot(HaveOccurred())
97+
Expect(upgradeapiv1.PlanSpecValidated.IsTrue(plan)).To(BeFalse())
98+
Expect(upgradeapiv1.PlanSpecValidated.GetMessage(plan)).To(ContainSubstring("cannot specify both deleteEmptydirData and deleteLocalData"))
99+
100+
plan.Spec.Drain.DeleteLocalData = nil
101+
plan, err = e2e.UpdatePlan(plan)
102+
Expect(err).ToNot(HaveOccurred())
103+
104+
plan, err = e2e.WaitForPlanCondition(plan.Name, upgradeapiv1.PlanSpecValidated, 30*time.Second)
105+
Expect(err).ToNot(HaveOccurred())
106+
Expect(upgradeapiv1.PlanSpecValidated.IsTrue(plan)).To(BeTrue())
107+
108+
jobs, err = e2e.WaitForPlanJobs(plan, 1, 120*time.Second)
109+
Expect(err).ToNot(HaveOccurred())
110+
Expect(jobs).To(HaveLen(1))
111+
})
112+
It("should apply successfully after edit", func() {
113+
Expect(jobs).To(HaveLen(1))
114+
Expect(jobs[0].Status.Succeeded).To(BeNumerically("==", 1))
115+
Expect(jobs[0].Status.Failed).To(BeNumerically("==", 0))
116+
Expect(jobs[0].Spec.Template.Spec.InitContainers).To(HaveLen(1))
117+
Expect(jobs[0].Spec.Template.Spec.InitContainers[0].Args).To(ContainSubstring("csi-attacher"))
118+
})
119+
})
62120
})

pkg/apis/upgrade.cattle.io/v1/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import (
1515
var (
1616
// PlanLatestResolved indicates that the latest version as per the spec has been determined.
1717
PlanLatestResolved = condition.Cond("LatestResolved")
18+
// PlanSpecValidated indicates that the plan spec has been validated.
19+
PlanSpecValidated = condition.Cond("Validated")
1820
)
1921

2022
// +genclient

pkg/upgrade/handle_upgrade.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ func (ctl *Controller) handlePlans(ctx context.Context) error {
2828
return status, nil
2929
}
3030
logrus.Debugf("PLAN STATUS HANDLER: plan=%s/%s@%s, status=%+v", obj.Namespace, obj.Name, obj.ResourceVersion, status)
31+
32+
validated := upgradeapiv1.PlanSpecValidated
33+
validated.CreateUnknownIfNotExists(obj)
34+
if err := upgradeplan.Validate(obj); err != nil {
35+
validated.SetError(obj, "Error", err)
36+
return upgradeplan.DigestStatus(obj, secretsCache)
37+
}
38+
validated.False(obj)
39+
validated.SetError(obj, "PlanIsValid", nil)
40+
3141
resolved := upgradeapiv1.PlanLatestResolved
3242
resolved.CreateUnknownIfNotExists(obj)
3343
if obj.Spec.Version == "" && obj.Spec.Channel == "" {
@@ -66,6 +76,9 @@ func (ctl *Controller) handlePlans(ctx context.Context) error {
6676
return objects, status, nil
6777
}
6878
logrus.Debugf("PLAN GENERATING HANDLER: plan=%s/%s@%s, status=%+v", obj.Namespace, obj.Name, obj.ResourceVersion, status)
79+
if !upgradeapiv1.PlanSpecValidated.IsTrue(obj) {
80+
return objects, status, nil
81+
}
6982
if !upgradeapiv1.PlanLatestResolved.IsTrue(obj) {
7083
return objects, status, nil
7184
}

pkg/upgrade/plan/plan.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ const (
3131
)
3232

3333
var (
34+
ErrDrainDeleteConflict = fmt.Errorf("spec.drain cannot specify both deleteEmptydirData and deleteLocalData")
35+
3436
PollingInterval = func(defaultValue time.Duration) time.Duration {
3537
if str, ok := os.LookupEnv("SYSTEM_UPGRADE_PLAN_POLLING_INTERVAL"); ok {
3638
if d, err := time.ParseDuration(str); err != nil {
@@ -228,3 +230,13 @@ func sha256sum(s ...string) string {
228230
}
229231
return fmt.Sprintf("%x", h.Sum(nil))
230232
}
233+
234+
// Validate performs validation of the plan spec, raising errors for any conflicting or invalid settings.
235+
func Validate(plan *upgradeapiv1.Plan) error {
236+
if drainSpec := plan.Spec.Drain; drainSpec != nil {
237+
if drainSpec.DeleteEmptydirData != nil && drainSpec.DeleteLocalData != nil {
238+
return ErrDrainDeleteConflict
239+
}
240+
}
241+
return nil
242+
}

0 commit comments

Comments
 (0)