...

Source file src/github.com/chaos-mesh/chaos-mesh/pkg/dashboard/apiserver/archive/archive.go

Documentation: github.com/chaos-mesh/chaos-mesh/pkg/dashboard/apiserver/archive

     1  // Copyright 2021 Chaos Mesh Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  
    16  package archive
    17  
    18  import (
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"net/http"
    23  	"reflect"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/gin-gonic/gin"
    28  	"github.com/jinzhu/gorm"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  
    31  	"github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
    32  	config "github.com/chaos-mesh/chaos-mesh/pkg/config/dashboard"
    33  	u "github.com/chaos-mesh/chaos-mesh/pkg/dashboard/apiserver/utils"
    34  	"github.com/chaos-mesh/chaos-mesh/pkg/dashboard/core"
    35  )
    36  
    37  // Service defines a handler service for archives.
    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  func NewService(
    47  	archive core.ExperimentStore,
    48  	archiveSchedule core.ScheduleStore,
    49  	event core.EventStore,
    50  	workflowStore core.WorkflowStore,
    51  	conf *config.ChaosDashboardConfig,
    52  ) *Service {
    53  	return &Service{
    54  		archive:         archive,
    55  		archiveSchedule: archiveSchedule,
    56  		event:           event,
    57  		workflowStore:   workflowStore,
    58  		conf:            conf,
    59  	}
    60  }
    61  
    62  // Register archives RouterGroup.
    63  func Register(r *gin.RouterGroup, s *Service) {
    64  	endpoint := r.Group("/archives")
    65  	endpoint.Use(func(c *gin.Context) {
    66  		u.AuthMiddleware(c, s.conf)
    67  	})
    68  
    69  	endpoint.GET("", s.list)
    70  	endpoint.GET("/:uid", s.get)
    71  	endpoint.DELETE("/:uid", s.delete)
    72  	endpoint.DELETE("", s.batchDelete)
    73  
    74  	endpoint.GET("/schedules", s.listSchedule)
    75  	endpoint.GET("/schedules/:uid", s.detailSchedule)
    76  	endpoint.DELETE("/schedules/:uid", s.deleteSchedule)
    77  	endpoint.DELETE("/schedules", s.batchDeleteSchedule)
    78  
    79  	endpoint.GET("/workflows", s.listWorkflow)
    80  	endpoint.GET("/workflows/:uid", s.detailWorkflow)
    81  	endpoint.DELETE("/workflows/:uid", s.deleteWorkflow)
    82  	endpoint.DELETE("/workflows", s.batchDeleteWorkflow)
    83  }
    84  
    85  // Archive defines the basic information of an archive.
    86  type Archive = core.ObjectBase
    87  
    88  // Detail represents an archive instance.
    89  type Detail struct {
    90  	Archive
    91  	KubeObject core.KubeObjectDesc `json:"kube_object"`
    92  }
    93  
    94  // @Summary Get archived chaos experiments.
    95  // @Description Get archived chaos experiments.
    96  // @Tags archives
    97  // @Produce json
    98  // @Param namespace query string false "namespace"
    99  // @Param name query string false "name"
   100  // @Param kind query string false "kind" Enums(PodChaos, IOChaos, NetworkChaos, TimeChaos, KernelChaos, StressChaos)
   101  // @Success 200 {array} Archive
   102  // @Router /archives [get]
   103  // @Failure 500 {object} utils.APIError
   104  func (s *Service) list(c *gin.Context) {
   105  	kind := c.Query("kind")
   106  	name := c.Query("name")
   107  	ns := c.Query("namespace")
   108  	if len(ns) == 0 && !s.conf.ClusterScoped &&
   109  		len(s.conf.TargetNamespace) != 0 {
   110  		ns = s.conf.TargetNamespace
   111  	}
   112  
   113  	metas, err := s.archive.ListMeta(context.Background(), kind, ns, name, true)
   114  	if err != nil {
   115  		c.Status(http.StatusInternalServerError)
   116  		_ = c.Error(u.ErrInternalServer.NewWithNoMessage())
   117  		return
   118  	}
   119  
   120  	archives := make([]Archive, 0)
   121  
   122  	for _, meta := range metas {
   123  		archives = append(archives, Archive{
   124  			UID:       meta.UID,
   125  			Kind:      meta.Kind,
   126  			Namespace: meta.Namespace,
   127  			Name:      meta.Name,
   128  			Created:   meta.StartTime.Format(time.RFC3339),
   129  		})
   130  	}
   131  
   132  	c.JSON(http.StatusOK, archives)
   133  }
   134  
   135  // @Summary Get an archived chaos experiment.
   136  // @Description Get the archived chaos experiment's detail by uid.
   137  // @Tags archives
   138  // @Produce json
   139  // @Param uid path string true "the archive uid"
   140  // @Success 200 {object} Detail
   141  // @Failure 404 {object} utils.APIError
   142  // @Failure 500 {object} utils.APIError
   143  // @Router /archives/{uid} [get]
   144  func (s *Service) get(c *gin.Context) {
   145  	uid := c.Param("uid")
   146  	exp, err := s.archive.FindByUID(context.Background(), uid)
   147  	if err != nil {
   148  		if gorm.IsRecordNotFoundError(err) {
   149  			u.SetAPIError(c, u.ErrNotFound.New("Experiment "+uid+" not found"))
   150  		} else {
   151  			u.SetAPIError(c, u.ErrInternalServer.WrapWithNoMessage(err))
   152  		}
   153  
   154  		return
   155  	}
   156  
   157  	chaos := v1alpha1.AllKinds()[exp.Kind].SpawnObject()
   158  	_ = json.Unmarshal([]byte(exp.Experiment), chaos)
   159  
   160  	c.JSON(http.StatusOK, &Detail{
   161  		Archive: Archive{
   162  			UID:       exp.UID,
   163  			Kind:      exp.Kind,
   164  			Name:      exp.Name,
   165  			Namespace: exp.Namespace,
   166  			Created:   exp.StartTime.Format(time.RFC3339),
   167  		},
   168  		KubeObject: core.KubeObjectDesc{
   169  			TypeMeta: metav1.TypeMeta{
   170  				APIVersion: reflect.ValueOf(chaos).Elem().FieldByName("APIVersion").String(),
   171  				Kind:       reflect.ValueOf(chaos).Elem().FieldByName("Kind").String(),
   172  			},
   173  			Meta: core.KubeObjectMeta{
   174  				Namespace:   reflect.ValueOf(chaos).Elem().FieldByName("Namespace").String(),
   175  				Name:        reflect.ValueOf(chaos).Elem().FieldByName("Name").String(),
   176  				Labels:      reflect.ValueOf(chaos).Elem().FieldByName("Labels").Interface().(map[string]string),
   177  				Annotations: reflect.ValueOf(chaos).Elem().FieldByName("Annotations").Interface().(map[string]string),
   178  			},
   179  			Spec: reflect.ValueOf(chaos).Elem().FieldByName("Spec").Interface(),
   180  		},
   181  	})
   182  }
   183  
   184  // @Summary Delete the specified archived experiment.
   185  // @Description Delete the specified archived experiment.
   186  // @Tags archives
   187  // @Produce json
   188  // @Param uid path string true "uid"
   189  // @Success 200 {object} utils.Response
   190  // @Failure 500 {object} utils.APIError
   191  // @Router /archives/{uid} [delete]
   192  func (s *Service) delete(c *gin.Context) {
   193  	var (
   194  		err error
   195  		exp *core.Experiment
   196  	)
   197  
   198  	uid := c.Param("uid")
   199  
   200  	if exp, err = s.archive.FindByUID(context.Background(), uid); err != nil {
   201  		if gorm.IsRecordNotFoundError(err) {
   202  			c.Status(http.StatusInternalServerError)
   203  			_ = c.Error(u.ErrBadRequest.New("the archived experiment is not found"))
   204  		} else {
   205  			c.Status(http.StatusInternalServerError)
   206  			_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
   207  		}
   208  		return
   209  	}
   210  
   211  	if err = s.archive.Delete(context.Background(), exp); err != nil {
   212  		c.Status(http.StatusInternalServerError)
   213  		_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
   214  	} else {
   215  		if err = s.event.DeleteByUID(context.Background(), uid); err != nil {
   216  			c.Status(http.StatusInternalServerError)
   217  			_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
   218  		} else {
   219  			c.JSON(http.StatusOK, u.ResponseSuccess)
   220  		}
   221  	}
   222  }
   223  
   224  // @Summary Delete the specified archived experiment.
   225  // @Description Delete the specified archived experiment.
   226  // @Tags archives
   227  // @Produce json
   228  // @Param uids query string true "uids"
   229  // @Success 200 {object} utils.Response
   230  // @Failure 500 {object} utils.APIError
   231  // @Router /archives [delete]
   232  func (s *Service) batchDelete(c *gin.Context) {
   233  	var (
   234  		err      error
   235  		uidSlice []string
   236  	)
   237  
   238  	uids := c.Query("uids")
   239  	if uids == "" {
   240  		c.Status(http.StatusBadRequest)
   241  		_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(fmt.Errorf("uids cannot be empty")))
   242  		return
   243  	}
   244  	uidSlice = strings.Split(uids, ",")
   245  
   246  	if err = s.archive.DeleteByUIDs(context.Background(), uidSlice); err != nil {
   247  		_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
   248  		c.Status(http.StatusInternalServerError)
   249  		return
   250  	}
   251  	if err = s.event.DeleteByUIDs(context.Background(), uidSlice); err != nil {
   252  		_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
   253  		c.Status(http.StatusInternalServerError)
   254  		return
   255  	}
   256  
   257  	c.JSON(http.StatusOK, u.ResponseSuccess)
   258  }
   259  
   260  // @Summary Get archived schedule experiments.
   261  // @Description Get archived schedule experiments.
   262  // @Tags archives
   263  // @Produce json
   264  // @Param namespace query string false "namespace"
   265  // @Param name query string false "name"
   266  // @Success 200 {array} Archive
   267  // @Router /archives/schedules [get]
   268  // @Failure 500 {object} utils.APIError
   269  func (s *Service) listSchedule(c *gin.Context) {
   270  	name := c.Query("name")
   271  	ns := c.Query("namespace")
   272  
   273  	metas, err := s.archiveSchedule.ListMeta(context.Background(), ns, name, true)
   274  	if err != nil {
   275  		c.Status(http.StatusInternalServerError)
   276  		_ = c.Error(u.ErrInternalServer.NewWithNoMessage())
   277  		return
   278  	}
   279  
   280  	archives := make([]Archive, 0)
   281  
   282  	for _, meta := range metas {
   283  		archives = append(archives, Archive{
   284  			UID:       meta.UID,
   285  			Kind:      meta.Kind,
   286  			Namespace: meta.Namespace,
   287  			Name:      meta.Name,
   288  			Created:   meta.StartTime.Format(time.RFC3339),
   289  		})
   290  	}
   291  
   292  	c.JSON(http.StatusOK, archives)
   293  }
   294  
   295  // @Summary Get the detail of an archived schedule experiment.
   296  // @Description Get the detail of an archived schedule experiment.
   297  // @Tags archives
   298  // @Produce json
   299  // @Param uid query string true "uid"
   300  // @Success 200 {object} Detail
   301  // @Router /archives/schedules/{uid} [get]
   302  // @Failure 500 {object} utils.APIError
   303  func (s *Service) detailSchedule(c *gin.Context) {
   304  	var (
   305  		err    error
   306  		detail Detail
   307  	)
   308  	uid := c.Param("uid")
   309  
   310  	if uid == "" {
   311  		c.Status(http.StatusBadRequest)
   312  		_ = c.Error(u.ErrBadRequest.New("uid cannot be empty"))
   313  		return
   314  	}
   315  
   316  	exp, err := s.archiveSchedule.FindByUID(context.Background(), uid)
   317  	if err != nil {
   318  		if gorm.IsRecordNotFoundError(err) {
   319  			c.Status(http.StatusInternalServerError)
   320  			_ = c.Error(u.ErrBadRequest.New("the archive schedule is not found"))
   321  		} else {
   322  			c.Status(http.StatusInternalServerError)
   323  			_ = c.Error(u.ErrInternalServer.NewWithNoMessage())
   324  		}
   325  		return
   326  	}
   327  
   328  	sch := &v1alpha1.Schedule{}
   329  	if err := json.Unmarshal([]byte(exp.Schedule), &sch); err != nil {
   330  		c.Status(http.StatusInternalServerError)
   331  		_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
   332  		return
   333  	}
   334  
   335  	detail = Detail{
   336  		Archive: Archive{
   337  			UID:       exp.UID,
   338  			Kind:      exp.Kind,
   339  			Name:      exp.Name,
   340  			Namespace: exp.Namespace,
   341  			Created:   exp.StartTime.Format(time.RFC3339),
   342  		},
   343  		KubeObject: core.KubeObjectDesc{
   344  			TypeMeta: metav1.TypeMeta{
   345  				APIVersion: sch.APIVersion,
   346  				Kind:       sch.Kind,
   347  			},
   348  			Meta: core.KubeObjectMeta{
   349  				Name:        sch.Name,
   350  				Namespace:   sch.Namespace,
   351  				Labels:      sch.Labels,
   352  				Annotations: sch.Annotations,
   353  			},
   354  			Spec: sch.Spec,
   355  		},
   356  	}
   357  
   358  	c.JSON(http.StatusOK, detail)
   359  }
   360  
   361  // @Summary Delete the specified archived schedule.
   362  // @Description Delete the specified archived schedule.
   363  // @Tags archives
   364  // @Produce json
   365  // @Param uid path string true "uid"
   366  // @Success 200 {object} utils.Response
   367  // @Failure 500 {object} utils.APIError
   368  // @Router /archives/schedules/{uid} [delete]
   369  func (s *Service) deleteSchedule(c *gin.Context) {
   370  	var (
   371  		err error
   372  		exp *core.Schedule
   373  	)
   374  
   375  	uid := c.Param("uid")
   376  
   377  	if exp, err = s.archiveSchedule.FindByUID(context.Background(), uid); err != nil {
   378  		if gorm.IsRecordNotFoundError(err) {
   379  			c.Status(http.StatusInternalServerError)
   380  			_ = c.Error(u.ErrBadRequest.New("the archived schedule is not found"))
   381  		} else {
   382  			c.Status(http.StatusInternalServerError)
   383  			_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
   384  		}
   385  		return
   386  	}
   387  
   388  	if err = s.archiveSchedule.Delete(context.Background(), exp); err != nil {
   389  		c.Status(http.StatusInternalServerError)
   390  		_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
   391  	} else {
   392  		if err = s.event.DeleteByUID(context.Background(), uid); err != nil {
   393  			c.Status(http.StatusInternalServerError)
   394  			_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
   395  		} else {
   396  			c.JSON(http.StatusOK, u.ResponseSuccess)
   397  		}
   398  	}
   399  }
   400  
   401  // @Summary Delete the specified archived schedule.
   402  // @Description Delete the specified archived schedule.
   403  // @Tags archives
   404  // @Produce json
   405  // @Param uids query string true "uids"
   406  // @Success 200 {object} utils.Response
   407  // @Failure 500 {object} utils.APIError
   408  // @Router /archives/schedules [delete]
   409  func (s *Service) batchDeleteSchedule(c *gin.Context) {
   410  	var (
   411  		err      error
   412  		uidSlice []string
   413  	)
   414  
   415  	uids := c.Query("uids")
   416  	if uids == "" {
   417  		c.Status(http.StatusBadRequest)
   418  		_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(fmt.Errorf("uids cannot be empty")))
   419  		return
   420  	}
   421  	uidSlice = strings.Split(uids, ",")
   422  
   423  	if err = s.archiveSchedule.DeleteByUIDs(context.Background(), uidSlice); err != nil {
   424  		_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
   425  		c.Status(http.StatusInternalServerError)
   426  		return
   427  	}
   428  	if err = s.event.DeleteByUIDs(context.Background(), uidSlice); err != nil {
   429  		_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
   430  		c.Status(http.StatusInternalServerError)
   431  		return
   432  	}
   433  
   434  	c.JSON(http.StatusOK, u.ResponseSuccess)
   435  }
   436  
   437  // @Summary Get archived workflow.
   438  // @Description Get archived workflow.
   439  // @Tags archives
   440  // @Produce json
   441  // @Param namespace query string false "namespace"
   442  // @Param name query string false "name"
   443  // @Success 200 {array} Archive
   444  // @Router /archives/workflows [get]
   445  // @Failure 500 {object} utils.APIError
   446  func (s *Service) listWorkflow(c *gin.Context) {
   447  	name := c.Query("name")
   448  	ns := c.Query("namespace")
   449  
   450  	metas, err := s.workflowStore.ListMeta(context.Background(), ns, name, true)
   451  	if err != nil {
   452  		c.Status(http.StatusInternalServerError)
   453  		_ = c.Error(u.ErrInternalServer.NewWithNoMessage())
   454  		return
   455  	}
   456  
   457  	archives := make([]Archive, 0)
   458  
   459  	for _, meta := range metas {
   460  		archives = append(archives, Archive{
   461  			UID:       meta.UID,
   462  			Kind:      v1alpha1.KindWorkflow,
   463  			Namespace: meta.Namespace,
   464  			Name:      meta.Name,
   465  			Created:   meta.CreatedAt.Format(time.RFC3339),
   466  		})
   467  	}
   468  
   469  	c.JSON(http.StatusOK, archives)
   470  }
   471  
   472  // @Summary Get the detail of an archived workflow.
   473  // @Description Get the detail of an archived workflow.
   474  // @Tags archives
   475  // @Produce json
   476  // @Param uid query string true "uid"
   477  // @Success 200 {object} Detail
   478  // @Router /archives/workflows/{uid} [get]
   479  // @Failure 500 {object} utils.APIError
   480  func (s *Service) detailWorkflow(c *gin.Context) {
   481  	var (
   482  		err    error
   483  		detail Detail
   484  	)
   485  	uid := c.Param("uid")
   486  
   487  	if uid == "" {
   488  		c.Status(http.StatusBadRequest)
   489  		_ = c.Error(u.ErrBadRequest.New("uid cannot be empty"))
   490  		return
   491  	}
   492  
   493  	meta, err := s.workflowStore.FindByUID(context.Background(), uid)
   494  	if err != nil {
   495  		if gorm.IsRecordNotFoundError(err) {
   496  			c.Status(http.StatusInternalServerError)
   497  			_ = c.Error(u.ErrBadRequest.New("the archive schedule is not found"))
   498  		} else {
   499  			c.Status(http.StatusInternalServerError)
   500  			_ = c.Error(u.ErrInternalServer.NewWithNoMessage())
   501  		}
   502  		return
   503  	}
   504  
   505  	workflow := &v1alpha1.Workflow{}
   506  	if err := json.Unmarshal([]byte(meta.Workflow), &workflow); err != nil {
   507  		c.Status(http.StatusInternalServerError)
   508  		_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
   509  		return
   510  	}
   511  
   512  	detail = Detail{
   513  		Archive: Archive{
   514  			UID:       meta.UID,
   515  			Kind:      v1alpha1.KindWorkflow,
   516  			Name:      meta.Name,
   517  			Namespace: meta.Namespace,
   518  			Created:   meta.CreatedAt.Format(time.RFC3339),
   519  		},
   520  		KubeObject: core.KubeObjectDesc{
   521  			TypeMeta: metav1.TypeMeta{
   522  				APIVersion: workflow.APIVersion,
   523  				Kind:       workflow.Kind,
   524  			},
   525  			Meta: core.KubeObjectMeta{
   526  				Name:        workflow.Name,
   527  				Namespace:   workflow.Namespace,
   528  				Labels:      workflow.Labels,
   529  				Annotations: workflow.Annotations,
   530  			},
   531  			Spec: workflow.Spec,
   532  		},
   533  	}
   534  
   535  	c.JSON(http.StatusOK, detail)
   536  }
   537  
   538  // @Summary Delete the specified archived workflow.
   539  // @Description Delete the specified archived workflow.
   540  // @Tags archives
   541  // @Produce json
   542  // @Param uid path string true "uid"
   543  // @Success 200 {object} utils.Response
   544  // @Failure 500 {object} utils.APIError
   545  // @Router /archives/workflows/{uid} [delete]
   546  func (s *Service) deleteWorkflow(c *gin.Context) {
   547  	var (
   548  		err error
   549  	)
   550  
   551  	uid := c.Param("uid")
   552  
   553  	if err = s.workflowStore.DeleteByUID(context.Background(), uid); err != nil {
   554  		c.Status(http.StatusInternalServerError)
   555  		_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
   556  	} else {
   557  		if err = s.event.DeleteByUID(context.Background(), uid); err != nil {
   558  			c.Status(http.StatusInternalServerError)
   559  			_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
   560  		} else {
   561  			c.JSON(http.StatusOK, u.ResponseSuccess)
   562  		}
   563  	}
   564  }
   565  
   566  // @Summary Delete the specified archived workflows.
   567  // @Description Delete the specified archived workflows.
   568  // @Tags archives
   569  // @Produce json
   570  // @Param uids query string true "uids"
   571  // @Success 200 {object} utils.Response
   572  // @Failure 500 {object} utils.APIError
   573  // @Router /archives/workflows [delete]
   574  func (s *Service) batchDeleteWorkflow(c *gin.Context) {
   575  	var (
   576  		err      error
   577  		uidSlice []string
   578  	)
   579  
   580  	uids := c.Query("uids")
   581  	if uids == "" {
   582  		c.Status(http.StatusBadRequest)
   583  		_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(fmt.Errorf("uids cannot be empty")))
   584  		return
   585  	}
   586  	uidSlice = strings.Split(uids, ",")
   587  
   588  	if err = s.workflowStore.DeleteByUIDs(context.Background(), uidSlice); err != nil {
   589  		_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
   590  		c.Status(http.StatusInternalServerError)
   591  		return
   592  	}
   593  	if err = s.event.DeleteByUIDs(context.Background(), uidSlice); err != nil {
   594  		_ = c.Error(u.ErrInternalServer.WrapWithNoMessage(err))
   595  		c.Status(http.StatusInternalServerError)
   596  		return
   597  	}
   598  
   599  	c.JSON(http.StatusOK, u.ResponseSuccess)
   600  }
   601