1
2
3
4
5
6
7
8
9
10
11
12
13
14 package schedule
15
16 import (
17 "context"
18 "encoding/json"
19 "fmt"
20 "net/http"
21 "reflect"
22 "sort"
23 "strings"
24 "time"
25
26 "github.com/gin-gonic/gin"
27 "github.com/jinzhu/gorm"
28 apierrors "k8s.io/apimachinery/pkg/api/errors"
29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30 "k8s.io/apimachinery/pkg/runtime"
31 "k8s.io/apimachinery/pkg/types"
32 "k8s.io/client-go/util/retry"
33 ctrl "sigs.k8s.io/controller-runtime"
34 "sigs.k8s.io/controller-runtime/pkg/client"
35 "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
36
37 "github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
38 "github.com/chaos-mesh/chaos-mesh/pkg/apiserver/utils"
39 "github.com/chaos-mesh/chaos-mesh/pkg/clientpool"
40 dashboardconfig "github.com/chaos-mesh/chaos-mesh/pkg/config/dashboard"
41 "github.com/chaos-mesh/chaos-mesh/pkg/core"
42 )
43
44 var log = ctrl.Log.WithName("schedule api")
45
46
47 type Service struct {
48 schedule core.ScheduleStore
49 event core.EventStore
50 conf *dashboardconfig.ChaosDashboardConfig
51 scheme *runtime.Scheme
52 }
53
54
55 func NewService(
56 schedule core.ScheduleStore,
57 event core.EventStore,
58 conf *dashboardconfig.ChaosDashboardConfig,
59 scheme *runtime.Scheme,
60 ) *Service {
61 return &Service{
62 schedule: schedule,
63 event: event,
64 conf: conf,
65 scheme: scheme,
66 }
67 }
68
69
70 func Register(r *gin.RouterGroup, s *Service) {
71 endpoint := r.Group("/schedules")
72
73 endpoint.GET("", s.listSchedules)
74 endpoint.GET("/:uid", s.getScheduleDetail)
75 endpoint.POST("/", s.createSchedule)
76 endpoint.PUT("/", s.updateSchedule)
77 endpoint.DELETE("/:uid", s.deleteSchedule)
78 endpoint.DELETE("/", s.batchDeleteSchedule)
79 endpoint.PUT("/pause/:uid", s.pauseSchedule)
80 endpoint.PUT("/start/:uid", s.startSchedule)
81 }
82
83
84 type Base struct {
85 Kind string `json:"kind"`
86 Namespace string `json:"namespace"`
87 Name string `json:"name"`
88 }
89
90
91 type Schedule struct {
92 Base
93 UID string `json:"uid"`
94 Created string `json:"created_at"`
95 Status string `json:"status"`
96 }
97
98
99 type Detail struct {
100 Schedule
101 YAML core.KubeObjectDesc `json:"kube_object"`
102 ExperimentUIDs []string `json:"experiment_uids"`
103 }
104
105 type parseScheduleFunc func(*core.ScheduleInfo) v1alpha1.ScheduleItem
106
107
108 type StatusResponse struct {
109 Status string `json:"status"`
110 }
111
112 type pauseFlag bool
113
114 const (
115 PauseSchedule pauseFlag = true
116 StartSchedule pauseFlag = false
117 )
118
119
120
121
122
123
124
125
126
127
128 func (s *Service) createSchedule(c *gin.Context) {
129 kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
130 if err != nil {
131 _ = c.Error(utils.ErrInvalidRequest.WrapWithNoMessage(err))
132 return
133 }
134
135 exp := &core.ScheduleInfo{}
136 if err := c.ShouldBindJSON(exp); err != nil {
137 c.Status(http.StatusBadRequest)
138 _ = c.Error(utils.ErrInvalidRequest.WrapWithNoMessage(err))
139 return
140 }
141
142 sch := &v1alpha1.Schedule{
143 ObjectMeta: metav1.ObjectMeta{
144 Name: exp.Name,
145 Namespace: exp.Namespace,
146 Labels: exp.Labels,
147 Annotations: exp.Annotations,
148 },
149 Spec: v1alpha1.ScheduleSpec{
150 Schedule: exp.Schedule,
151 ConcurrencyPolicy: exp.ConcurrencyPolicy,
152 HistoryLimit: exp.HistoryLimit,
153 Type: v1alpha1.ScheduleTemplateType(exp.Target.Kind),
154 },
155 }
156 if exp.StartingDeadlineSeconds != nil {
157 sch.Spec.StartingDeadlineSeconds = exp.StartingDeadlineSeconds
158 }
159
160 parseFuncs := map[string]parseScheduleFunc{
161 v1alpha1.KindPodChaos: parsePodChaos,
162 v1alpha1.KindNetworkChaos: parseNetworkChaos,
163 v1alpha1.KindIOChaos: parseIOChaos,
164 v1alpha1.KindStressChaos: parseStressChaos,
165 v1alpha1.KindTimeChaos: parseTimeChaos,
166 v1alpha1.KindKernelChaos: parseKernelChaos,
167 v1alpha1.KindDNSChaos: parseDNSChaos,
168 v1alpha1.KindAWSChaos: parseAWSChaos,
169 v1alpha1.KindGCPChaos: parseGCPChaos,
170 }
171
172 f, ok := parseFuncs[exp.Target.Kind]
173 if !ok {
174 c.Status(http.StatusBadRequest)
175 _ = c.Error(utils.ErrInvalidRequest.New(exp.Target.Kind + " is not supported"))
176 return
177 }
178 embedChaos := f(exp)
179 sch.Spec.ScheduleItem = embedChaos
180
181 if err := kubeCli.Create(context.Background(), sch); err != nil {
182 c.Status(http.StatusInternalServerError)
183 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
184 return
185 }
186
187 c.JSON(http.StatusOK, exp)
188 }
189
190 func parsePodChaos(exp *core.ScheduleInfo) v1alpha1.ScheduleItem {
191 chaos := &v1alpha1.PodChaos{
192 ObjectMeta: metav1.ObjectMeta{
193 Name: exp.Name,
194 Namespace: exp.Namespace,
195 Labels: exp.Labels,
196 Annotations: exp.Annotations,
197 },
198 Spec: v1alpha1.PodChaosSpec{
199 ContainerSelector: v1alpha1.ContainerSelector{
200 PodSelector: v1alpha1.PodSelector{
201 Selector: exp.Scope.ParseSelector(),
202 Mode: v1alpha1.PodMode(exp.Scope.Mode),
203 Value: exp.Scope.Value,
204 },
205 ContainerNames: exp.Target.PodChaos.ContainerNames,
206 },
207 Action: v1alpha1.PodChaosAction(exp.Target.PodChaos.Action),
208 GracePeriod: exp.Target.PodChaos.GracePeriod,
209 },
210 }
211
212 if exp.Duration != "" {
213 chaos.Spec.Duration = &exp.Duration
214 }
215
216 return v1alpha1.ScheduleItem{
217 EmbedChaos: v1alpha1.EmbedChaos{PodChaos: &chaos.Spec},
218 }
219 }
220
221 func parseNetworkChaos(exp *core.ScheduleInfo) v1alpha1.ScheduleItem {
222 chaos := &v1alpha1.NetworkChaos{
223 ObjectMeta: metav1.ObjectMeta{
224 Name: exp.Name,
225 Namespace: exp.Namespace,
226 Labels: exp.Labels,
227 Annotations: exp.Annotations,
228 },
229 Spec: v1alpha1.NetworkChaosSpec{
230 PodSelector: v1alpha1.PodSelector{
231 Selector: exp.Scope.ParseSelector(),
232 Mode: v1alpha1.PodMode(exp.Scope.Mode),
233 Value: exp.Scope.Value,
234 },
235 Action: v1alpha1.NetworkChaosAction(exp.Target.NetworkChaos.Action),
236 TcParameter: v1alpha1.TcParameter{
237 Delay: exp.Target.NetworkChaos.Delay,
238 Loss: exp.Target.NetworkChaos.Loss,
239 Duplicate: exp.Target.NetworkChaos.Duplicate,
240 Corrupt: exp.Target.NetworkChaos.Corrupt,
241 Bandwidth: exp.Target.NetworkChaos.Bandwidth,
242 },
243 Direction: v1alpha1.Direction(exp.Target.NetworkChaos.Direction),
244 ExternalTargets: exp.Target.NetworkChaos.ExternalTargets,
245 },
246 }
247
248 if exp.Target.NetworkChaos.TargetScope != nil {
249 chaos.Spec.Target = &v1alpha1.PodSelector{
250 Selector: exp.Target.NetworkChaos.TargetScope.ParseSelector(),
251 Mode: v1alpha1.PodMode(exp.Target.NetworkChaos.TargetScope.Mode),
252 Value: exp.Target.NetworkChaos.TargetScope.Value,
253 }
254 }
255
256 if exp.Duration != "" {
257 chaos.Spec.Duration = &exp.Duration
258 }
259
260 return v1alpha1.ScheduleItem{
261 EmbedChaos: v1alpha1.EmbedChaos{NetworkChaos: &chaos.Spec},
262 }
263 }
264
265 func parseIOChaos(exp *core.ScheduleInfo) v1alpha1.ScheduleItem {
266 chaos := &v1alpha1.IOChaos{
267 ObjectMeta: metav1.ObjectMeta{
268 Name: exp.Name,
269 Namespace: exp.Namespace,
270 Labels: exp.Labels,
271 Annotations: exp.Annotations,
272 },
273 Spec: v1alpha1.IOChaosSpec{
274 ContainerSelector: v1alpha1.ContainerSelector{
275 PodSelector: v1alpha1.PodSelector{
276 Selector: exp.Scope.ParseSelector(),
277 Mode: v1alpha1.PodMode(exp.Scope.Mode),
278 Value: exp.Scope.Value,
279 },
280 ContainerNames: []string{exp.Target.IOChaos.ContainerName},
281 },
282 Action: v1alpha1.IOChaosType(exp.Target.IOChaos.Action),
283 Delay: exp.Target.IOChaos.Delay,
284 Errno: exp.Target.IOChaos.Errno,
285 Attr: exp.Target.IOChaos.Attr,
286 Mistake: exp.Target.IOChaos.Mistake,
287 Path: exp.Target.IOChaos.Path,
288 Methods: exp.Target.IOChaos.Methods,
289 Percent: exp.Target.IOChaos.Percent,
290 VolumePath: exp.Target.IOChaos.VolumePath,
291 },
292 }
293
294 if exp.Duration != "" {
295 chaos.Spec.Duration = &exp.Duration
296 }
297
298 return v1alpha1.ScheduleItem{
299 EmbedChaos: v1alpha1.EmbedChaos{IOChaos: &chaos.Spec},
300 }
301 }
302
303 func parseTimeChaos(exp *core.ScheduleInfo) v1alpha1.ScheduleItem {
304 chaos := &v1alpha1.TimeChaos{
305 ObjectMeta: metav1.ObjectMeta{
306 Name: exp.Name,
307 Namespace: exp.Namespace,
308 Labels: exp.Labels,
309 Annotations: exp.Annotations,
310 },
311 Spec: v1alpha1.TimeChaosSpec{
312 ContainerSelector: v1alpha1.ContainerSelector{
313 PodSelector: v1alpha1.PodSelector{
314 Selector: exp.Scope.ParseSelector(),
315 Mode: v1alpha1.PodMode(exp.Scope.Mode),
316 Value: exp.Scope.Value,
317 },
318 ContainerNames: exp.Target.TimeChaos.ContainerNames,
319 },
320 TimeOffset: exp.Target.TimeChaos.TimeOffset,
321 ClockIds: exp.Target.TimeChaos.ClockIDs,
322 },
323 }
324
325 if exp.Duration != "" {
326 chaos.Spec.Duration = &exp.Duration
327 }
328
329 return v1alpha1.ScheduleItem{
330 EmbedChaos: v1alpha1.EmbedChaos{TimeChaos: &chaos.Spec},
331 }
332 }
333
334 func parseKernelChaos(exp *core.ScheduleInfo) v1alpha1.ScheduleItem {
335 chaos := &v1alpha1.KernelChaos{
336 ObjectMeta: metav1.ObjectMeta{
337 Name: exp.Name,
338 Namespace: exp.Namespace,
339 Labels: exp.Labels,
340 Annotations: exp.Annotations,
341 },
342 Spec: v1alpha1.KernelChaosSpec{
343 PodSelector: v1alpha1.PodSelector{
344 Selector: exp.Scope.ParseSelector(),
345 Mode: v1alpha1.PodMode(exp.Scope.Mode),
346 Value: exp.Scope.Value,
347 },
348 FailKernRequest: exp.Target.KernelChaos.FailKernRequest,
349 },
350 }
351
352 if exp.Duration != "" {
353 chaos.Spec.Duration = &exp.Duration
354 }
355
356 return v1alpha1.ScheduleItem{
357 EmbedChaos: v1alpha1.EmbedChaos{KernelChaos: &chaos.Spec},
358 }
359 }
360
361 func parseStressChaos(exp *core.ScheduleInfo) v1alpha1.ScheduleItem {
362 var stressors *v1alpha1.Stressors
363
364
365 if exp.Target.StressChaos.Stressors.CPUStressor.Workers <= 0 && exp.Target.StressChaos.Stressors.MemoryStressor.Workers > 0 {
366 stressors = &v1alpha1.Stressors{
367 MemoryStressor: exp.Target.StressChaos.Stressors.MemoryStressor,
368 }
369 } else if exp.Target.StressChaos.Stressors.MemoryStressor.Workers <= 0 && exp.Target.StressChaos.Stressors.CPUStressor.Workers > 0 {
370 stressors = &v1alpha1.Stressors{
371 CPUStressor: exp.Target.StressChaos.Stressors.CPUStressor,
372 }
373 } else {
374 stressors = exp.Target.StressChaos.Stressors
375 }
376
377 chaos := &v1alpha1.StressChaos{
378 ObjectMeta: metav1.ObjectMeta{
379 Name: exp.Name,
380 Namespace: exp.Namespace,
381 Labels: exp.Labels,
382 Annotations: exp.Annotations,
383 },
384 Spec: v1alpha1.StressChaosSpec{
385 ContainerSelector: v1alpha1.ContainerSelector{
386 PodSelector: v1alpha1.PodSelector{
387 Selector: exp.Scope.ParseSelector(),
388 Mode: v1alpha1.PodMode(exp.Scope.Mode),
389 Value: exp.Scope.Value,
390 },
391 ContainerNames: exp.Target.StressChaos.ContainerNames,
392 },
393 Stressors: stressors,
394 StressngStressors: exp.Target.StressChaos.StressngStressors,
395 },
396 }
397
398 if exp.Duration != "" {
399 chaos.Spec.Duration = &exp.Duration
400 }
401
402 return v1alpha1.ScheduleItem{
403 EmbedChaos: v1alpha1.EmbedChaos{StressChaos: &chaos.Spec},
404 }
405 }
406
407 func parseDNSChaos(exp *core.ScheduleInfo) v1alpha1.ScheduleItem {
408 chaos := &v1alpha1.DNSChaos{
409 ObjectMeta: metav1.ObjectMeta{
410 Name: exp.Name,
411 Namespace: exp.Namespace,
412 Labels: exp.Labels,
413 Annotations: exp.Annotations,
414 },
415 Spec: v1alpha1.DNSChaosSpec{
416 Action: v1alpha1.DNSChaosAction(exp.Target.DNSChaos.Action),
417 ContainerSelector: v1alpha1.ContainerSelector{
418 PodSelector: v1alpha1.PodSelector{
419 Selector: exp.Scope.ParseSelector(),
420 Mode: v1alpha1.PodMode(exp.Scope.Mode),
421 Value: exp.Scope.Value,
422 },
423 ContainerNames: exp.Target.DNSChaos.ContainerNames,
424 },
425 DomainNamePatterns: exp.Target.DNSChaos.DomainNamePatterns,
426 },
427 }
428
429 if exp.Duration != "" {
430 chaos.Spec.Duration = &exp.Duration
431 }
432
433 return v1alpha1.ScheduleItem{
434 EmbedChaos: v1alpha1.EmbedChaos{DNSChaos: &chaos.Spec},
435 }
436 }
437
438 func parseAWSChaos(exp *core.ScheduleInfo) v1alpha1.ScheduleItem {
439 chaos := &v1alpha1.AWSChaos{
440 ObjectMeta: metav1.ObjectMeta{
441 Name: exp.Name,
442 Namespace: exp.Namespace,
443 Labels: exp.Labels,
444 Annotations: exp.Annotations,
445 },
446 Spec: v1alpha1.AWSChaosSpec{
447 Action: v1alpha1.AWSChaosAction(exp.Target.AWSChaos.Action),
448 SecretName: exp.Target.AWSChaos.SecretName,
449 AWSSelector: v1alpha1.AWSSelector{
450 AWSRegion: exp.Target.AWSChaos.AWSRegion,
451 Ec2Instance: exp.Target.AWSChaos.Ec2Instance,
452 EbsVolume: exp.Target.AWSChaos.EbsVolume,
453 DeviceName: exp.Target.AWSChaos.DeviceName,
454 },
455 },
456 }
457
458 if exp.Duration != "" {
459 chaos.Spec.Duration = &exp.Duration
460 }
461
462 return v1alpha1.ScheduleItem{
463 EmbedChaos: v1alpha1.EmbedChaos{AWSChaos: &chaos.Spec},
464 }
465 }
466
467 func parseGCPChaos(exp *core.ScheduleInfo) v1alpha1.ScheduleItem {
468 chaos := &v1alpha1.GCPChaos{
469 ObjectMeta: metav1.ObjectMeta{
470 Name: exp.Name,
471 Namespace: exp.Namespace,
472 Labels: exp.Labels,
473 Annotations: exp.Annotations,
474 },
475 Spec: v1alpha1.GCPChaosSpec{
476 Action: v1alpha1.GCPChaosAction(exp.Target.GCPChaos.Action),
477 SecretName: exp.Target.GCPChaos.SecretName,
478 GCPSelector: v1alpha1.GCPSelector{
479 Project: exp.Target.GCPChaos.Project,
480 Zone: exp.Target.GCPChaos.Zone,
481 Instance: exp.Target.GCPChaos.Instance,
482 DeviceNames: exp.Target.GCPChaos.DeviceNames,
483 },
484 },
485 }
486
487 if exp.Duration != "" {
488 chaos.Spec.Duration = &exp.Duration
489 }
490
491 return v1alpha1.ScheduleItem{
492 EmbedChaos: v1alpha1.EmbedChaos{GCPChaos: &chaos.Spec},
493 }
494 }
495
496
497
498
499
500
501
502
503
504
505 func (s *Service) listSchedules(c *gin.Context) {
506 kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
507 if err != nil {
508 _ = c.Error(utils.ErrInvalidRequest.WrapWithNoMessage(err))
509 return
510 }
511
512 name := c.Query("name")
513 ns := c.Query("namespace")
514
515 if !s.conf.ClusterScoped {
516 log.Info("Overwrite namespace within namespace scoped mode", "origin", ns, "new", s.conf.TargetNamespace)
517 ns = s.conf.TargetNamespace
518 }
519
520 ScheduleList := v1alpha1.ScheduleList{}
521 sches := make([]*Schedule, 0)
522 if err := kubeCli.List(context.Background(), &ScheduleList, &client.ListOptions{Namespace: ns}); err != nil {
523 c.Status(http.StatusInternalServerError)
524 utils.SetErrorForGinCtx(c, err)
525 return
526 }
527 for _, schedule := range ScheduleList.Items {
528 if name != "" && schedule.Name != name {
529 continue
530 }
531 sches = append(sches, &Schedule{
532 Base: Base{
533 Kind: string(schedule.Spec.Type),
534 Namespace: schedule.Namespace,
535 Name: schedule.Name,
536 },
537 UID: string(schedule.UID),
538 Created: schedule.CreationTimestamp.Format(time.RFC3339),
539 Status: string(utils.GetScheduleState(schedule)),
540 })
541 }
542
543 sort.Slice(sches, func(i, j int) bool {
544 return sches[i].Created > sches[j].Created
545 })
546
547 c.JSON(http.StatusOK, sches)
548 }
549
550
551
552
553
554
555
556
557
558
559 func (s *Service) getScheduleDetail(c *gin.Context) {
560 var (
561 sch *core.Schedule
562 schDetail Detail
563 )
564
565 kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
566 if err != nil {
567 _ = c.Error(utils.ErrInvalidRequest.WrapWithNoMessage(err))
568 return
569 }
570
571 uid := c.Param("uid")
572 if sch, err = s.schedule.FindByUID(context.Background(), uid); err != nil {
573 if gorm.IsRecordNotFoundError(err) {
574 c.Status(http.StatusInternalServerError)
575 _ = c.Error(utils.ErrInvalidRequest.New("the schedule is not found"))
576 } else {
577 c.Status(http.StatusInternalServerError)
578 _ = c.Error(utils.ErrInternalServer.NewWithNoMessage())
579 }
580 return
581 }
582
583 ns := sch.Namespace
584 name := sch.Name
585
586 if !s.conf.ClusterScoped && ns != s.conf.TargetNamespace {
587 c.Status(http.StatusBadRequest)
588 _ = c.Error(utils.ErrInvalidRequest.New("the namespace is not supported in cluster scoped mode"))
589 return
590 }
591
592 schedule := &v1alpha1.Schedule{}
593
594 scheduleKey := types.NamespacedName{Namespace: ns, Name: name}
595 if err := kubeCli.Get(context.Background(), scheduleKey, schedule); err != nil {
596 c.Status(http.StatusInternalServerError)
597 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
598 return
599 }
600
601 gvk, err := apiutil.GVKForObject(schedule, s.scheme)
602 if err != nil {
603 c.Status(http.StatusInternalServerError)
604 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
605 return
606 }
607
608 UIDList := make([]string, 0)
609 kind, ok := v1alpha1.AllScheduleItemKinds()[string(schedule.Spec.Type)]
610 if !ok {
611 c.Status(http.StatusInternalServerError)
612 _ = c.Error(utils.ErrInvalidRequest.New("the kind is not supported"))
613 return
614 }
615 list := kind.ChaosList.DeepCopyObject()
616 selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{
617 MatchLabels: map[string]string{"managed-by": schedule.Name},
618 })
619 if err != nil {
620 c.Status(http.StatusInternalServerError)
621 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
622 return
623 }
624
625 err = kubeCli.List(context.Background(), list, &client.ListOptions{
626 Namespace: ns,
627 LabelSelector: selector,
628 })
629 if err != nil {
630 c.Status(http.StatusInternalServerError)
631 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
632 return
633 }
634 items := reflect.ValueOf(list).Elem().FieldByName("Items")
635 for i := 0; i < items.Len(); i++ {
636 if schedule.Spec.Type != v1alpha1.ScheduleTypeWorkflow {
637 item := items.Index(i).Addr().Interface().(v1alpha1.InnerObject)
638 UIDList = append(UIDList, item.GetChaos().UID)
639 } else {
640 workflow := items.Index(i).Addr().Interface().(*v1alpha1.Workflow)
641 UIDList = append(UIDList, string(workflow.UID))
642 }
643 }
644
645 schDetail = Detail{
646 Schedule: Schedule{
647 Base: Base{
648 Kind: string(schedule.Spec.Type),
649 Namespace: schedule.Namespace,
650 Name: schedule.Name,
651 },
652 UID: string(schedule.UID),
653 Created: schedule.CreationTimestamp.Format(time.RFC3339),
654 Status: string(utils.GetScheduleState(*schedule)),
655 },
656 YAML: core.KubeObjectDesc{
657 TypeMeta: metav1.TypeMeta{
658 Kind: gvk.Kind,
659 APIVersion: gvk.GroupVersion().String(),
660 },
661 Meta: core.KubeObjectMeta{
662 Name: schedule.Name,
663 Namespace: schedule.Namespace,
664 Labels: schedule.Labels,
665 Annotations: schedule.Annotations,
666 },
667 Spec: schedule.Spec,
668 },
669 ExperimentUIDs: UIDList,
670 }
671
672 c.JSON(http.StatusOK, schDetail)
673 }
674
675
676
677
678
679
680
681
682
683
684
685
686 func (s *Service) deleteSchedule(c *gin.Context) {
687 var (
688 exp *core.Schedule
689 )
690
691 kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
692 if err != nil {
693 _ = c.Error(utils.ErrInvalidRequest.WrapWithNoMessage(err))
694 return
695 }
696
697 uid := c.Param("uid")
698 if exp, err = s.schedule.FindByUID(context.Background(), uid); err != nil {
699 if gorm.IsRecordNotFoundError(err) {
700 c.Status(http.StatusInternalServerError)
701 _ = c.Error(utils.ErrInvalidRequest.New("the experiment is not found"))
702 } else {
703 c.Status(http.StatusInternalServerError)
704 _ = c.Error(utils.ErrInternalServer.NewWithNoMessage())
705 }
706 return
707 }
708
709 ns := exp.Namespace
710 name := exp.Name
711
712 ctx := context.TODO()
713 scheduleKey := types.NamespacedName{Namespace: ns, Name: name}
714 schedule := &v1alpha1.Schedule{}
715
716 if err := kubeCli.Get(ctx, scheduleKey, schedule); err != nil {
717 if apierrors.IsNotFound(err) {
718 c.Status(http.StatusNotFound)
719 _ = c.Error(utils.ErrNotFound.NewWithNoMessage())
720 } else {
721 c.Status(http.StatusInternalServerError)
722 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
723 }
724 return
725 }
726
727 if err := kubeCli.Delete(ctx, schedule, &client.DeleteOptions{}); err != nil {
728 if apierrors.IsNotFound(err) {
729 c.Status(http.StatusNotFound)
730 _ = c.Error(utils.ErrNotFound.NewWithNoMessage())
731 } else {
732 c.Status(http.StatusInternalServerError)
733 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
734 }
735 return
736 }
737
738 c.JSON(http.StatusOK, StatusResponse{Status: "success"})
739 }
740
741
742
743
744
745
746
747
748
749
750 func (s *Service) updateSchedule(c *gin.Context) {
751 kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
752 if err != nil {
753 _ = c.Error(utils.ErrInvalidRequest.WrapWithNoMessage(err))
754 return
755 }
756
757 exp := &core.KubeObjectDesc{}
758 if err := c.ShouldBindJSON(exp); err != nil {
759 c.Status(http.StatusBadRequest)
760 _ = c.Error(utils.ErrInvalidRequest.WrapWithNoMessage(err))
761 return
762 }
763
764 err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
765 return s.updateScheduleFun(exp, kubeCli)
766 })
767 if err != nil {
768 if apierrors.IsNotFound(err) {
769 c.Status(http.StatusNotFound)
770 _ = c.Error(utils.ErrNotFound.WrapWithNoMessage(err))
771 } else {
772 c.Status(http.StatusInternalServerError)
773 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
774 }
775 return
776 }
777 c.JSON(http.StatusOK, exp)
778 }
779
780 func (s *Service) updateScheduleFun(exp *core.KubeObjectDesc, kubeCli client.Client) error {
781 sch := &v1alpha1.Schedule{}
782 meta := &exp.Meta
783 key := types.NamespacedName{Namespace: meta.Namespace, Name: meta.Name}
784
785 if err := kubeCli.Get(context.Background(), key, sch); err != nil {
786 return err
787 }
788
789 sch.SetLabels(meta.Labels)
790 sch.SetAnnotations(meta.Annotations)
791
792 var spec v1alpha1.ScheduleSpec
793 bytes, err := json.Marshal(exp.Spec)
794 if err != nil {
795 return err
796 }
797 if err = json.Unmarshal(bytes, &spec); err != nil {
798 return err
799 }
800 sch.Spec = spec
801
802 return kubeCli.Update(context.Background(), sch)
803 }
804
805
806
807
808
809
810
811
812
813
814
815 func (s *Service) batchDeleteSchedule(c *gin.Context) {
816 var (
817 exp *core.Schedule
818 errFlag bool
819 uidSlice []string
820 )
821
822 kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
823 if err != nil {
824 _ = c.Error(utils.ErrInvalidRequest.WrapWithNoMessage(err))
825 return
826 }
827
828 uids := c.Query("uids")
829 if uids == "" {
830 c.Status(http.StatusBadRequest)
831 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(fmt.Errorf("uids cannot be empty")))
832 return
833 }
834 uidSlice = strings.Split(uids, ",")
835 errFlag = false
836
837 for _, uid := range uidSlice {
838 if exp, err = s.schedule.FindByUID(context.Background(), uid); err != nil {
839 if gorm.IsRecordNotFoundError(err) {
840 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(fmt.Errorf("delete experiment uid (%s) error, because the experiment is not found", uid)))
841 } else {
842 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(fmt.Errorf("delete experiment uid (%s) error, because %s", uid, err.Error())))
843 }
844 errFlag = true
845 continue
846 }
847
848 ns := exp.Namespace
849 name := exp.Name
850
851 ctx := context.TODO()
852 scheduleKey := types.NamespacedName{Namespace: ns, Name: name}
853 schedule := &v1alpha1.Schedule{}
854
855 if err := kubeCli.Get(ctx, scheduleKey, schedule); err != nil {
856 if apierrors.IsNotFound(err) {
857 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(fmt.Errorf("delete experiment uid (%s) error, because the chaos is not found", uid)))
858 } else {
859 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(fmt.Errorf("delete experiment uid (%s) error, because %s", uid, err.Error())))
860 }
861 errFlag = true
862 continue
863 }
864
865 if err := kubeCli.Delete(ctx, schedule, &client.DeleteOptions{}); err != nil {
866 if apierrors.IsNotFound(err) {
867 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(fmt.Errorf("delete experiment uid (%s) error, because the chaos is not found", uid)))
868 } else {
869 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(fmt.Errorf("delete experiment uid (%s) error, because %s", uid, err.Error())))
870 }
871 errFlag = true
872 continue
873 }
874 }
875 if errFlag {
876 c.Status(http.StatusInternalServerError)
877 } else {
878 c.JSON(http.StatusOK, StatusResponse{Status: "success"})
879 }
880 }
881
882
883
884
885
886
887
888
889
890
891
892 func (s *Service) pauseSchedule(c *gin.Context) {
893 kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
894 if err != nil {
895 _ = c.Error(utils.ErrInvalidRequest.WrapWithNoMessage(err))
896 return
897 }
898
899 uid := c.Param("uid")
900 err = s.pauseOrStartSchedule(uid, PauseSchedule, kubeCli)
901
902 if err != nil {
903 if gorm.IsRecordNotFoundError(err) || apierrors.IsNotFound(err) {
904 c.Status(http.StatusNotFound)
905 _ = c.Error(utils.ErrInvalidRequest.New("the schedule is not found"))
906 }
907 c.Status(http.StatusInternalServerError)
908 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
909 return
910 }
911
912 c.JSON(http.StatusOK, StatusResponse{Status: "success"})
913 }
914
915
916
917
918
919
920
921
922
923
924
925 func (s *Service) startSchedule(c *gin.Context) {
926 kubeCli, err := clientpool.ExtractTokenAndGetClient(c.Request.Header)
927 if err != nil {
928 _ = c.Error(utils.ErrInvalidRequest.WrapWithNoMessage(err))
929 return
930 }
931
932 uid := c.Param("uid")
933 err = s.pauseOrStartSchedule(uid, StartSchedule, kubeCli)
934
935 if err != nil {
936 if gorm.IsRecordNotFoundError(err) || apierrors.IsNotFound(err) {
937 c.Status(http.StatusNotFound)
938 _ = c.Error(utils.ErrInvalidRequest.New("the schedule is not found"))
939 }
940 c.Status(http.StatusInternalServerError)
941 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
942 return
943 }
944
945 c.JSON(http.StatusOK, StatusResponse{Status: "success"})
946 }
947
948 func (s *Service) pauseOrStartSchedule(uid string, flag pauseFlag, kubeCli client.Client) error {
949 var (
950 err error
951 schedule *core.Schedule
952 pauseAnnotation string
953 )
954
955 if schedule, err = s.schedule.FindByUID(context.Background(), uid); err != nil {
956 return err
957 }
958
959 exp := &Base{
960 Name: schedule.Name,
961 Namespace: schedule.Namespace,
962 }
963
964 if flag == PauseSchedule {
965 pauseAnnotation = "true"
966 } else {
967 pauseAnnotation = "false"
968 }
969
970 annotations := map[string]string{
971 v1alpha1.PauseAnnotationKey: pauseAnnotation,
972 }
973
974 return s.patchSchedule(exp, annotations, kubeCli)
975 }
976
977 func (s *Service) patchSchedule(exp *Base, annotations map[string]string, kubeCli client.Client) error {
978 sch := &v1alpha1.Schedule{}
979 key := types.NamespacedName{Namespace: exp.Namespace, Name: exp.Name}
980
981 if err := kubeCli.Get(context.Background(), key, sch); err != nil {
982 return err
983 }
984
985 var mergePatch []byte
986 mergePatch, _ = json.Marshal(map[string]interface{}{
987 "metadata": map[string]interface{}{
988 "annotations": annotations,
989 },
990 })
991
992 return kubeCli.Patch(context.Background(),
993 sch,
994 client.ConstantPatch(types.MergePatchType, mergePatch))
995 }
996