...

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