1
2
3
4
5
6
7
8
9
10
11
12
13
14 package twophase
15
16 import (
17 "context"
18 "errors"
19 "testing"
20 "time"
21
22 . "github.com/onsi/ginkgo"
23 . "github.com/onsi/gomega"
24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25 "k8s.io/apimachinery/pkg/types"
26 "k8s.io/client-go/kubernetes/scheme"
27 ctrl "sigs.k8s.io/controller-runtime"
28 "sigs.k8s.io/controller-runtime/pkg/client/fake"
29 "sigs.k8s.io/controller-runtime/pkg/envtest"
30
31 "github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
32 "github.com/chaos-mesh/chaos-mesh/pkg/mock"
33 ctx "github.com/chaos-mesh/chaos-mesh/pkg/router/context"
34 )
35
36 func TestStateMachine(t *testing.T) {
37 RegisterFailHandler(Fail)
38
39 RunSpecsWithDefaultAndCustomReporters(t,
40 "Twophase StateMachine Suite",
41 []Reporter{envtest.NewlineReporter{}})
42 }
43
44 var _ = Describe("TwoPhase StateMachine", func() {
45 Context("TwoPhase", func() {
46
47 statuses := []v1alpha1.ExperimentPhase{
48 v1alpha1.ExperimentPhaseFailed,
49 v1alpha1.ExperimentPhaseFinished,
50 v1alpha1.ExperimentPhasePaused,
51 v1alpha1.ExperimentPhaseRunning,
52 v1alpha1.ExperimentPhaseWaiting,
53 }
54
55 It("StateMachine Target Finish", func() {
56 defer mock.With("MockApplyError", errors.New("ApplyError"))()
57 defer mock.With("MockRecoverError", errors.New("RecoverError"))()
58
59 for _, status := range statuses {
60 now := time.Now()
61 sm := setupStateMachineWithStatus(status)
62
63 updated, err := sm.run(context.TODO(), v1alpha1.ExperimentPhaseFinished, now)
64
65 if status == v1alpha1.ExperimentPhaseRunning {
66 Expect(updated).To(Equal(true))
67 Expect(err).To(HaveOccurred())
68 Expect(err.Error()).To(ContainSubstring("RecoverError"))
69 Expect(sm.Chaos.GetStatus().Experiment.Phase).To(Equal(v1alpha1.ExperimentPhaseRunning))
70 Expect(sm.Chaos.GetStatus().FailedMessage).To(ContainSubstring("RecoverError"))
71
72 return
73 } else if status == v1alpha1.ExperimentPhaseFinished {
74 Expect(updated).To(Equal(false))
75 } else if status == v1alpha1.ExperimentPhaseFailed {
76 Expect(updated).To(Equal(true))
77 Expect(err).To(HaveOccurred())
78 Expect(err.Error()).To(ContainSubstring("RecoverError"))
79 Expect(sm.Chaos.GetStatus().FailedMessage).To(ContainSubstring("RecoverError"))
80
81 return
82 } else {
83 Expect(updated).To(Equal(true))
84 }
85
86 Expect(err).ToNot(HaveOccurred())
87 }
88 })
89
90 It("StateMachine Target Paused", func() {
91 defer mock.With("MockApplyError", errors.New("ApplyError"))()
92 defer mock.With("MockRecoverError", errors.New("RecoverError"))()
93
94 for _, status := range statuses {
95 now := time.Now()
96 sm := setupStateMachineWithStatus(status)
97
98 updated, err := sm.run(context.TODO(), v1alpha1.ExperimentPhasePaused, now)
99
100 if status == v1alpha1.ExperimentPhaseRunning {
101 Expect(updated).To(Equal(true))
102 Expect(err).To(HaveOccurred())
103 Expect(err.Error()).To(ContainSubstring("RecoverError"))
104 Expect(sm.Chaos.GetStatus().Experiment.Phase).To(Equal(v1alpha1.ExperimentPhaseRunning))
105 Expect(sm.Chaos.GetStatus().FailedMessage).To(ContainSubstring("RecoverError"))
106
107 return
108 } else if status == v1alpha1.ExperimentPhaseFinished {
109 Expect(err).To(HaveOccurred())
110 Expect(err.Error()).To(ContainSubstring("turn from"))
111
112 return
113 } else if status != v1alpha1.ExperimentPhasePaused {
114 Expect(updated).To(Equal(true))
115 } else {
116 Expect(updated).To(Equal(false))
117 }
118
119 Expect(err).ToNot(HaveOccurred())
120 }
121 })
122
123 It("StateMachine Target Running", func() {
124 defer mock.With("MockApplyError", errors.New("ApplyError"))()
125 defer mock.With("MockRecoverError", errors.New("RecoverError"))()
126
127 for _, status := range statuses {
128 now := time.Now()
129 sm := setupStateMachineWithStatus(status)
130
131 updated, err := sm.run(context.TODO(), v1alpha1.ExperimentPhaseRunning, now)
132
133 if status == v1alpha1.ExperimentPhaseFinished {
134 Expect(err).To(HaveOccurred())
135 Expect(err.Error()).To(ContainSubstring("turn from"))
136 } else if status == v1alpha1.ExperimentPhaseRunning {
137 Expect(updated).To(Equal(false))
138 Expect(err).ToNot(HaveOccurred())
139 } else if status != v1alpha1.ExperimentPhasePaused {
140 Expect(err).To(HaveOccurred())
141 Expect(err.Error()).To(ContainSubstring("ApplyError"))
142 }
143 }
144 })
145
146 It("Pause", func() {
147
148
149
150 now, err := time.Parse(time.RFC3339, "2020-12-07T13:10:00+00:00")
151 Expect(err).ToNot(HaveOccurred())
152 sm := setupStateMachineWithStatus(v1alpha1.ExperimentPhaseUninitialized)
153
154 updated, err := sm.run(context.TODO(), v1alpha1.ExperimentPhaseRunning, now)
155 Expect(err).ToNot(HaveOccurred())
156 Expect(updated).To(Equal(true))
157 Expect(sm.Chaos.GetStatus().Experiment.Phase).To(Equal(v1alpha1.ExperimentPhaseRunning))
158
159 now = now.Add(time.Minute)
160 updated, err = sm.run(context.TODO(), v1alpha1.ExperimentPhasePaused, now)
161 Expect(err).ToNot(HaveOccurred())
162 Expect(updated).To(Equal(true))
163 Expect(sm.Chaos.GetStatus().Experiment.Phase).To(Equal(v1alpha1.ExperimentPhasePaused))
164
165
166 now, err = time.Parse(time.RFC3339, "2020-12-07T13:55:00+00:00")
167 Expect(err).ToNot(HaveOccurred())
168 updated, err = sm.run(context.TODO(), v1alpha1.ExperimentPhaseRunning, now)
169 Expect(err).ToNot(HaveOccurred())
170 Expect(updated).To(Equal(true))
171 Expect(sm.Chaos.GetStatus().Experiment.Phase).To(Equal(v1alpha1.ExperimentPhaseRunning))
172
173 now = now.Add(time.Minute)
174 updated, err = sm.run(context.TODO(), v1alpha1.ExperimentPhasePaused, now)
175 Expect(err).ToNot(HaveOccurred())
176 Expect(updated).To(Equal(true))
177 Expect(sm.Chaos.GetStatus().Experiment.Phase).To(Equal(v1alpha1.ExperimentPhasePaused))
178
179 now, err = time.Parse(time.RFC3339, "2020-12-07T14:06:00+00:00")
180 Expect(err).ToNot(HaveOccurred())
181 updated, err = sm.run(context.TODO(), v1alpha1.ExperimentPhaseRunning, now)
182 Expect(err).ToNot(HaveOccurred())
183 Expect(updated).To(Equal(true))
184 Expect(sm.Chaos.GetStatus().Experiment.Phase).To(Equal(v1alpha1.ExperimentPhaseWaiting))
185
186 now, err = time.Parse(time.RFC3339, "2020-12-07T14:11:00+00:00")
187 Expect(err).ToNot(HaveOccurred())
188 updated, err = sm.run(context.TODO(), v1alpha1.ExperimentPhaseRunning, now)
189 Expect(err).ToNot(HaveOccurred())
190 Expect(updated).To(Equal(true))
191 Expect(sm.Chaos.GetStatus().Experiment.Phase).To(Equal(v1alpha1.ExperimentPhaseRunning))
192 })
193
194 It("StateMachine Unexpected State", func() {
195 var unexpectedState v1alpha1.ExperimentPhase = "wrong-state"
196 now := time.Now()
197
198 for _, status := range statuses {
199 sm := setupStateMachineWithStatus(status)
200 updated, err := sm.run(context.TODO(), unexpectedState, now)
201 Expect(updated).To(Equal(false))
202 Expect(err).To(HaveOccurred())
203 Expect(err.Error()).To(ContainSubstring("unexpected target phase"))
204 }
205
206 sm := setupStateMachineWithStatus(unexpectedState)
207 for _, status := range statuses {
208 updated, err := sm.run(context.TODO(), status, now)
209 Expect(updated).To(Equal(false))
210 Expect(err).To(HaveOccurred())
211 Expect(err.Error()).To(ContainSubstring("unexpected current phase"))
212 }
213 })
214 })
215
216 })
217
218 func setupStateMachineWithStatus(status v1alpha1.ExperimentPhase) *chaosStateMachine {
219 req := ctrl.Request{
220 NamespacedName: types.NamespacedName{
221 Name: "fakechaos-name",
222 Namespace: metav1.NamespaceDefault,
223 },
224 }
225 typeMeta := metav1.TypeMeta{
226 Kind: "PodChaos",
227 APIVersion: "v1",
228 }
229 objectMeta := metav1.ObjectMeta{
230 Namespace: metav1.NamespaceDefault,
231 Name: "fakechaos-name",
232 }
233
234 chaos := fakeTwoPhaseChaos{
235 TypeMeta: typeMeta,
236 ObjectMeta: objectMeta,
237 }
238
239 chaos.Status.Experiment.Phase = status
240 duration := "15m"
241 chaos.Duration = &duration
242 chaos.Scheduler = &v1alpha1.SchedulerSpec{
243 Cron: "@every 20m",
244 }
245
246 c := fake.NewFakeClientWithScheme(scheme.Scheme, &chaos)
247
248 r := Reconciler{
249 Endpoint: fakeEndpoint{},
250 Context: ctx.Context{
251 Client: c,
252 Log: ctrl.Log.WithName("controllers").WithName("TwoPhase"),
253 },
254 }
255
256 sm := chaosStateMachine{
257 Chaos: &chaos,
258 Req: req,
259 Reconciler: &r,
260 }
261
262 return &sm
263 }
264