1
2
3
4
5
6
7
8
9
10
11
12
13
14 package archive
15
16 import (
17 "context"
18 "encoding/json"
19 "fmt"
20 "net/http"
21 "strings"
22 "time"
23
24 "github.com/gin-gonic/gin"
25 "github.com/jinzhu/gorm"
26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 ctrl "sigs.k8s.io/controller-runtime"
28
29 "github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
30 "github.com/chaos-mesh/chaos-mesh/pkg/apiserver/utils"
31 config "github.com/chaos-mesh/chaos-mesh/pkg/config/dashboard"
32 "github.com/chaos-mesh/chaos-mesh/pkg/core"
33 )
34
35 var log = ctrl.Log.WithName("archive api")
36
37
38 type Service struct {
39 archive core.ExperimentStore
40 archiveSchedule core.ScheduleStore
41 event core.EventStore
42 workflowStore core.WorkflowStore
43 conf *config.ChaosDashboardConfig
44 }
45
46
47 func NewService(
48 archive core.ExperimentStore,
49 archiveSchedule core.ScheduleStore,
50 event core.EventStore,
51 workflowStore core.WorkflowStore,
52 conf *config.ChaosDashboardConfig,
53 ) *Service {
54 return &Service{
55 archive: archive,
56 archiveSchedule: archiveSchedule,
57 event: event,
58 workflowStore: workflowStore,
59 conf: conf,
60 }
61 }
62
63
64 type StatusResponse struct {
65 Status string `json:"status"`
66 }
67
68
69 func Register(r *gin.RouterGroup, s *Service) {
70 endpoint := r.Group("/archives")
71 endpoint.Use(func(c *gin.Context) {
72 utils.AuthRequired(c, s.conf.ClusterScoped, s.conf.TargetNamespace)
73 })
74
75 endpoint.GET("", s.list)
76 endpoint.GET("/detail", s.detail)
77 endpoint.DELETE("/:uid", s.delete)
78 endpoint.DELETE("/", s.batchDelete)
79
80 endpoint.GET("/schedules", s.listSchedule)
81 endpoint.GET("/schedules/:uid", s.detailSchedule)
82 endpoint.DELETE("/schedules/:uid", s.deleteSchedule)
83 endpoint.DELETE("/schedules", s.batchDeleteSchedule)
84
85 endpoint.GET("/workflows", s.listWorkflow)
86 endpoint.GET("/workflows/:uid", s.detailWorkflow)
87 endpoint.DELETE("/workflows/:uid", s.deleteWorkflow)
88 endpoint.DELETE("/workflows", s.batchDeleteWorkflow)
89 }
90
91
92 type Archive struct {
93 UID string `json:"uid"`
94 Kind string `json:"kind"`
95 Namespace string `json:"namespace"`
96 Name string `json:"name"`
97 CreatedAt time.Time `json:"created_at"`
98 }
99
100
101 type Detail struct {
102 Archive
103 KubeObject core.KubeObjectDesc `json:"kube_object"`
104 }
105
106
107
108
109
110
111
112
113
114
115
116 func (s *Service) list(c *gin.Context) {
117 kind := c.Query("kind")
118 name := c.Query("name")
119 ns := c.Query("namespace")
120 if len(ns) == 0 && !s.conf.ClusterScoped &&
121 len(s.conf.TargetNamespace) != 0 {
122 ns = s.conf.TargetNamespace
123 }
124
125 metas, err := s.archive.ListMeta(context.Background(), kind, ns, name, true)
126 if err != nil {
127 c.Status(http.StatusInternalServerError)
128 _ = c.Error(utils.ErrInternalServer.NewWithNoMessage())
129 return
130 }
131
132 archives := make([]Archive, 0)
133
134 for _, meta := range metas {
135 archives = append(archives, Archive{
136 UID: meta.UID,
137 Kind: meta.Kind,
138 Namespace: meta.Namespace,
139 Name: meta.Name,
140 CreatedAt: meta.StartTime,
141 })
142 }
143
144 c.JSON(http.StatusOK, archives)
145 }
146
147
148
149
150
151
152
153
154
155 func (s *Service) detail(c *gin.Context) {
156 var (
157 err error
158 kubeObject core.KubeObjectDesc
159 detail Detail
160 )
161 uid := c.Query("uid")
162 namespace := c.Query("namespace")
163 if len(namespace) == 0 && !s.conf.ClusterScoped &&
164 len(s.conf.TargetNamespace) != 0 {
165 namespace = s.conf.TargetNamespace
166 }
167
168 if uid == "" {
169 c.Status(http.StatusBadRequest)
170 _ = c.Error(utils.ErrInvalidRequest.New("uid cannot be empty"))
171 return
172 }
173
174 exp, err := s.archive.FindByUID(context.Background(), uid)
175 if err != nil {
176 if gorm.IsRecordNotFoundError(err) {
177 c.Status(http.StatusInternalServerError)
178 _ = c.Error(utils.ErrInvalidRequest.New("the archive is not found"))
179 } else {
180 c.Status(http.StatusInternalServerError)
181 _ = c.Error(utils.ErrInternalServer.NewWithNoMessage())
182 }
183 return
184 }
185
186 if len(namespace) != 0 && exp.Namespace != namespace {
187 c.Status(http.StatusBadRequest)
188 _ = c.Error(utils.ErrInvalidRequest.New("exp %s belong to namespace %s but not namespace %s", uid, exp.Namespace, namespace))
189 return
190 }
191
192 switch exp.Kind {
193 case v1alpha1.KindPodChaos:
194 kubeObject, err = exp.ParsePodChaos()
195 case v1alpha1.KindIOChaos:
196 kubeObject, err = exp.ParseIOChaos()
197 case v1alpha1.KindNetworkChaos:
198 kubeObject, err = exp.ParseNetworkChaos()
199 case v1alpha1.KindTimeChaos:
200 kubeObject, err = exp.ParseTimeChaos()
201 case v1alpha1.KindKernelChaos:
202 kubeObject, err = exp.ParseKernelChaos()
203 case v1alpha1.KindStressChaos:
204 kubeObject, err = exp.ParseStressChaos()
205 case v1alpha1.KindDNSChaos:
206 kubeObject, err = exp.ParseDNSChaos()
207 case v1alpha1.KindAWSChaos:
208 kubeObject, err = exp.ParseAWSChaos()
209 case v1alpha1.KindGCPChaos:
210 kubeObject, err = exp.ParseGCPChaos()
211 default:
212 err = fmt.Errorf("kind %s is not support", exp.Kind)
213 }
214 if err != nil {
215 c.Status(http.StatusInternalServerError)
216 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
217 return
218 }
219
220 detail = Detail{
221 Archive: Archive{
222 UID: exp.UID,
223 Kind: exp.Kind,
224 Name: exp.Name,
225 Namespace: exp.Namespace,
226 CreatedAt: exp.StartTime,
227 },
228 KubeObject: kubeObject,
229 }
230
231 c.JSON(http.StatusOK, detail)
232 }
233
234
235
236
237
238
239
240
241
242 func (s *Service) delete(c *gin.Context) {
243 var (
244 err error
245 exp *core.Experiment
246 )
247
248 uid := c.Param("uid")
249
250 if exp, err = s.archive.FindByUID(context.Background(), uid); err != nil {
251 if gorm.IsRecordNotFoundError(err) {
252 c.Status(http.StatusInternalServerError)
253 _ = c.Error(utils.ErrInvalidRequest.New("the archived experiment is not found"))
254 } else {
255 c.Status(http.StatusInternalServerError)
256 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
257 }
258 return
259 }
260
261 if err = s.archive.Delete(context.Background(), exp); err != nil {
262 c.Status(http.StatusInternalServerError)
263 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
264 } else {
265 if err = s.event.DeleteByUID(context.Background(), uid); err != nil {
266 c.Status(http.StatusInternalServerError)
267 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
268 } else {
269 c.JSON(http.StatusOK, StatusResponse{Status: "success"})
270 }
271 }
272 }
273
274
275
276
277
278
279
280
281
282 func (s *Service) batchDelete(c *gin.Context) {
283 var (
284 err error
285 uidSlice []string
286 )
287
288 uids := c.Query("uids")
289 if uids == "" {
290 c.Status(http.StatusBadRequest)
291 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(fmt.Errorf("uids cannot be empty")))
292 return
293 }
294 uidSlice = strings.Split(uids, ",")
295
296 if err = s.archive.DeleteByUIDs(context.Background(), uidSlice); err != nil {
297 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
298 c.Status(http.StatusInternalServerError)
299 return
300 }
301 if err = s.event.DeleteByUIDs(context.Background(), uidSlice); err != nil {
302 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
303 c.Status(http.StatusInternalServerError)
304 return
305 }
306
307 c.JSON(http.StatusOK, StatusResponse{Status: "success"})
308 }
309
310
311
312
313
314
315
316
317
318
319 func (s *Service) listSchedule(c *gin.Context) {
320 name := c.Query("name")
321 ns := c.Query("namespace")
322
323 metas, err := s.archiveSchedule.ListMeta(context.Background(), ns, name, true)
324 if err != nil {
325 c.Status(http.StatusInternalServerError)
326 _ = c.Error(utils.ErrInternalServer.NewWithNoMessage())
327 return
328 }
329
330 archives := make([]Archive, 0)
331
332 for _, meta := range metas {
333 archives = append(archives, Archive{
334 UID: meta.UID,
335 Kind: meta.Kind,
336 Namespace: meta.Namespace,
337 Name: meta.Name,
338 CreatedAt: meta.StartTime,
339 })
340 }
341
342 c.JSON(http.StatusOK, archives)
343 }
344
345
346
347
348
349
350
351
352
353 func (s *Service) detailSchedule(c *gin.Context) {
354 var (
355 err error
356 detail Detail
357 )
358 uid := c.Param("uid")
359
360 if uid == "" {
361 c.Status(http.StatusBadRequest)
362 _ = c.Error(utils.ErrInvalidRequest.New("uid cannot be empty"))
363 return
364 }
365
366 exp, err := s.archiveSchedule.FindByUID(context.Background(), uid)
367 if err != nil {
368 if gorm.IsRecordNotFoundError(err) {
369 c.Status(http.StatusInternalServerError)
370 _ = c.Error(utils.ErrInvalidRequest.New("the archive schedule is not found"))
371 } else {
372 c.Status(http.StatusInternalServerError)
373 _ = c.Error(utils.ErrInternalServer.NewWithNoMessage())
374 }
375 return
376 }
377
378 sch := &v1alpha1.Schedule{}
379 if err := json.Unmarshal([]byte(exp.Schedule), &sch); err != nil {
380 c.Status(http.StatusInternalServerError)
381 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
382 return
383 }
384
385 detail = Detail{
386 Archive: Archive{
387 UID: exp.UID,
388 Kind: exp.Kind,
389 Name: exp.Name,
390 Namespace: exp.Namespace,
391 CreatedAt: exp.StartTime,
392 },
393 KubeObject: core.KubeObjectDesc{
394 TypeMeta: metav1.TypeMeta{
395 APIVersion: sch.APIVersion,
396 Kind: sch.Kind,
397 },
398 Meta: core.KubeObjectMeta{
399 Name: sch.Name,
400 Namespace: sch.Namespace,
401 Labels: sch.Labels,
402 Annotations: sch.Annotations,
403 },
404 Spec: sch.Spec,
405 },
406 }
407
408 c.JSON(http.StatusOK, detail)
409 }
410
411
412
413
414
415
416
417
418
419 func (s *Service) deleteSchedule(c *gin.Context) {
420 var (
421 err error
422 exp *core.Schedule
423 )
424
425 uid := c.Param("uid")
426
427 if exp, err = s.archiveSchedule.FindByUID(context.Background(), uid); err != nil {
428 if gorm.IsRecordNotFoundError(err) {
429 c.Status(http.StatusInternalServerError)
430 _ = c.Error(utils.ErrInvalidRequest.New("the archived schedule is not found"))
431 } else {
432 c.Status(http.StatusInternalServerError)
433 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
434 }
435 return
436 }
437
438 if err = s.archiveSchedule.Delete(context.Background(), exp); err != nil {
439 c.Status(http.StatusInternalServerError)
440 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
441 } else {
442 if err = s.event.DeleteByUID(context.Background(), uid); err != nil {
443 c.Status(http.StatusInternalServerError)
444 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
445 } else {
446 c.JSON(http.StatusOK, StatusResponse{Status: "success"})
447 }
448 }
449 }
450
451
452
453
454
455
456
457
458
459 func (s *Service) batchDeleteSchedule(c *gin.Context) {
460 var (
461 err error
462 uidSlice []string
463 )
464
465 uids := c.Query("uids")
466 if uids == "" {
467 c.Status(http.StatusBadRequest)
468 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(fmt.Errorf("uids cannot be empty")))
469 return
470 }
471 uidSlice = strings.Split(uids, ",")
472
473 if err = s.archiveSchedule.DeleteByUIDs(context.Background(), uidSlice); err != nil {
474 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
475 c.Status(http.StatusInternalServerError)
476 return
477 }
478 if err = s.event.DeleteByUIDs(context.Background(), uidSlice); err != nil {
479 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
480 c.Status(http.StatusInternalServerError)
481 return
482 }
483
484 c.JSON(http.StatusOK, StatusResponse{Status: "success"})
485 }
486
487
488
489
490
491
492
493
494
495
496 func (s *Service) listWorkflow(c *gin.Context) {
497 name := c.Query("name")
498 ns := c.Query("namespace")
499
500 metas, err := s.workflowStore.ListMeta(context.Background(), ns, name, true)
501 if err != nil {
502 c.Status(http.StatusInternalServerError)
503 _ = c.Error(utils.ErrInternalServer.NewWithNoMessage())
504 return
505 }
506
507 archives := make([]Archive, 0)
508
509 for _, meta := range metas {
510 archives = append(archives, Archive{
511 UID: meta.UID,
512 Kind: v1alpha1.KindWorkflow,
513 Namespace: meta.Namespace,
514 Name: meta.Name,
515 CreatedAt: meta.CreatedAt,
516 })
517 }
518
519 c.JSON(http.StatusOK, archives)
520 }
521
522
523
524
525
526
527
528
529
530 func (s *Service) detailWorkflow(c *gin.Context) {
531 var (
532 err error
533 detail Detail
534 )
535 uid := c.Param("uid")
536
537 if uid == "" {
538 c.Status(http.StatusBadRequest)
539 _ = c.Error(utils.ErrInvalidRequest.New("uid cannot be empty"))
540 return
541 }
542
543 meta, err := s.workflowStore.FindByUID(context.Background(), uid)
544 if err != nil {
545 if gorm.IsRecordNotFoundError(err) {
546 c.Status(http.StatusInternalServerError)
547 _ = c.Error(utils.ErrInvalidRequest.New("the archive schedule is not found"))
548 } else {
549 c.Status(http.StatusInternalServerError)
550 _ = c.Error(utils.ErrInternalServer.NewWithNoMessage())
551 }
552 return
553 }
554
555 workflow := &v1alpha1.Workflow{}
556 if err := json.Unmarshal([]byte(meta.Workflow), &workflow); err != nil {
557 c.Status(http.StatusInternalServerError)
558 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
559 return
560 }
561
562 detail = Detail{
563 Archive: Archive{
564 UID: meta.UID,
565 Kind: v1alpha1.KindWorkflow,
566 Name: meta.Name,
567 Namespace: meta.Namespace,
568 CreatedAt: meta.CreatedAt,
569 },
570 KubeObject: core.KubeObjectDesc{
571 TypeMeta: metav1.TypeMeta{
572 APIVersion: workflow.APIVersion,
573 Kind: workflow.Kind,
574 },
575 Meta: core.KubeObjectMeta{
576 Name: workflow.Name,
577 Namespace: workflow.Namespace,
578 Labels: workflow.Labels,
579 Annotations: workflow.Annotations,
580 },
581 Spec: workflow.Spec,
582 },
583 }
584
585 c.JSON(http.StatusOK, detail)
586 }
587
588
589
590
591
592
593
594
595
596 func (s *Service) deleteWorkflow(c *gin.Context) {
597 var (
598 err error
599 )
600
601 uid := c.Param("uid")
602
603 if err = s.workflowStore.DeleteByUID(context.Background(), uid); err != nil {
604 c.Status(http.StatusInternalServerError)
605 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
606 } else {
607 if err = s.event.DeleteByUID(context.Background(), uid); err != nil {
608 c.Status(http.StatusInternalServerError)
609 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
610 } else {
611 c.JSON(http.StatusOK, StatusResponse{Status: "success"})
612 }
613 }
614 }
615
616
617
618
619
620
621
622
623
624 func (s *Service) batchDeleteWorkflow(c *gin.Context) {
625 var (
626 err error
627 uidSlice []string
628 )
629
630 uids := c.Query("uids")
631 if uids == "" {
632 c.Status(http.StatusBadRequest)
633 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(fmt.Errorf("uids cannot be empty")))
634 return
635 }
636 uidSlice = strings.Split(uids, ",")
637
638 if err = s.workflowStore.DeleteByUIDs(context.Background(), uidSlice); err != nil {
639 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
640 c.Status(http.StatusInternalServerError)
641 return
642 }
643 if err = s.event.DeleteByUIDs(context.Background(), uidSlice); err != nil {
644 _ = c.Error(utils.ErrInternalServer.WrapWithNoMessage(err))
645 c.Status(http.StatusInternalServerError)
646 return
647 }
648
649 c.JSON(http.StatusOK, StatusResponse{Status: "success"})
650 }
651