| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- package dashboards
- import (
- "github.com/grafana/grafana/pkg/util"
- "math/rand"
- "os"
- "path/filepath"
- "runtime"
- "testing"
- "time"
- "github.com/grafana/grafana/pkg/bus"
- "github.com/grafana/grafana/pkg/models"
- "github.com/grafana/grafana/pkg/services/dashboards"
- "github.com/grafana/grafana/pkg/log"
- . "github.com/smartystreets/goconvey/convey"
- )
- var (
- defaultDashboards = "testdata/test-dashboards/folder-one"
- brokenDashboards = "testdata/test-dashboards/broken-dashboards"
- oneDashboard = "testdata/test-dashboards/one-dashboard"
- containingId = "testdata/test-dashboards/containing-id"
- unprovision = "testdata/test-dashboards/unprovision"
- fakeService *fakeDashboardProvisioningService
- )
- func TestCreatingNewDashboardFileReader(t *testing.T) {
- Convey("creating new dashboard file reader", t, func() {
- cfg := &DashboardsAsConfig{
- Name: "Default",
- Type: "file",
- OrgId: 1,
- Folder: "",
- Options: map[string]interface{}{},
- }
- Convey("using path parameter", func() {
- cfg.Options["path"] = defaultDashboards
- reader, err := NewDashboardFileReader(cfg, log.New("test-logger"))
- So(err, ShouldBeNil)
- So(reader.Path, ShouldNotEqual, "")
- })
- Convey("using folder as options", func() {
- cfg.Options["folder"] = defaultDashboards
- reader, err := NewDashboardFileReader(cfg, log.New("test-logger"))
- So(err, ShouldBeNil)
- So(reader.Path, ShouldNotEqual, "")
- })
- Convey("using full path", func() {
- fullPath := "/var/lib/grafana/dashboards"
- if runtime.GOOS == "windows" {
- fullPath = `c:\var\lib\grafana`
- }
- cfg.Options["folder"] = fullPath
- reader, err := NewDashboardFileReader(cfg, log.New("test-logger"))
- So(err, ShouldBeNil)
- So(reader.Path, ShouldEqual, fullPath)
- So(filepath.IsAbs(reader.Path), ShouldBeTrue)
- })
- Convey("using relative path", func() {
- cfg.Options["folder"] = defaultDashboards
- reader, err := NewDashboardFileReader(cfg, log.New("test-logger"))
- So(err, ShouldBeNil)
- resolvedPath := reader.resolvePath(reader.Path)
- So(filepath.IsAbs(resolvedPath), ShouldBeTrue)
- })
- })
- }
- func TestDashboardFileReader(t *testing.T) {
- Convey("Dashboard file reader", t, func() {
- bus.ClearBusHandlers()
- origNewDashboardProvisioningService := dashboards.NewProvisioningService
- fakeService = mockDashboardProvisioningService()
- bus.AddHandler("test", mockGetDashboardQuery)
- logger := log.New("test.logger")
- Convey("Reading dashboards from disk", func() {
- cfg := &DashboardsAsConfig{
- Name: "Default",
- Type: "file",
- OrgId: 1,
- Folder: "",
- Options: map[string]interface{}{},
- }
- Convey("Can read default dashboard", func() {
- cfg.Options["path"] = defaultDashboards
- cfg.Folder = "Team A"
- reader, err := NewDashboardFileReader(cfg, logger)
- So(err, ShouldBeNil)
- err = reader.startWalkingDisk()
- So(err, ShouldBeNil)
- folders := 0
- dashboards := 0
- for _, i := range fakeService.inserted {
- if i.Dashboard.IsFolder {
- folders++
- } else {
- dashboards++
- }
- }
- So(folders, ShouldEqual, 1)
- So(dashboards, ShouldEqual, 2)
- })
- Convey("Can read default dashboard and replace old version in database", func() {
- cfg.Options["path"] = oneDashboard
- stat, _ := os.Stat(oneDashboard + "/dashboard1.json")
- fakeService.getDashboard = append(fakeService.getDashboard, &models.Dashboard{
- Updated: stat.ModTime().AddDate(0, 0, -1),
- Slug: "grafana",
- })
- reader, err := NewDashboardFileReader(cfg, logger)
- So(err, ShouldBeNil)
- err = reader.startWalkingDisk()
- So(err, ShouldBeNil)
- So(len(fakeService.inserted), ShouldEqual, 1)
- })
- Convey("Overrides id from dashboard.json files", func() {
- cfg.Options["path"] = containingId
- reader, err := NewDashboardFileReader(cfg, logger)
- So(err, ShouldBeNil)
- err = reader.startWalkingDisk()
- So(err, ShouldBeNil)
- So(len(fakeService.inserted), ShouldEqual, 1)
- })
- Convey("Invalid configuration should return error", func() {
- cfg := &DashboardsAsConfig{
- Name: "Default",
- Type: "file",
- OrgId: 1,
- Folder: "",
- }
- _, err := NewDashboardFileReader(cfg, logger)
- So(err, ShouldNotBeNil)
- })
- Convey("Broken dashboards should not cause error", func() {
- cfg.Options["path"] = brokenDashboards
- _, err := NewDashboardFileReader(cfg, logger)
- So(err, ShouldBeNil)
- })
- Convey("Two dashboard providers should be able to provisioned the same dashboard without uid", func() {
- cfg1 := &DashboardsAsConfig{Name: "1", Type: "file", OrgId: 1, Folder: "f1", Options: map[string]interface{}{"path": containingId}}
- cfg2 := &DashboardsAsConfig{Name: "2", Type: "file", OrgId: 1, Folder: "f2", Options: map[string]interface{}{"path": containingId}}
- reader1, err := NewDashboardFileReader(cfg1, logger)
- So(err, ShouldBeNil)
- err = reader1.startWalkingDisk()
- So(err, ShouldBeNil)
- reader2, err := NewDashboardFileReader(cfg2, logger)
- So(err, ShouldBeNil)
- err = reader2.startWalkingDisk()
- So(err, ShouldBeNil)
- var folderCount int
- var dashCount int
- for _, o := range fakeService.inserted {
- if o.Dashboard.IsFolder {
- folderCount++
- } else {
- dashCount++
- }
- }
- So(folderCount, ShouldEqual, 2)
- So(dashCount, ShouldEqual, 2)
- })
- })
- Convey("Should not create new folder if folder name is missing", func() {
- cfg := &DashboardsAsConfig{
- Name: "Default",
- Type: "file",
- OrgId: 1,
- Folder: "",
- Options: map[string]interface{}{
- "folder": defaultDashboards,
- },
- }
- _, err := getOrCreateFolderId(cfg, fakeService)
- So(err, ShouldEqual, ErrFolderNameMissing)
- })
- Convey("can get or Create dashboard folder", func() {
- cfg := &DashboardsAsConfig{
- Name: "Default",
- Type: "file",
- OrgId: 1,
- Folder: "TEAM A",
- Options: map[string]interface{}{
- "folder": defaultDashboards,
- },
- }
- folderId, err := getOrCreateFolderId(cfg, fakeService)
- So(err, ShouldBeNil)
- inserted := false
- for _, d := range fakeService.inserted {
- if d.Dashboard.IsFolder && d.Dashboard.Id == folderId {
- inserted = true
- }
- }
- So(len(fakeService.inserted), ShouldEqual, 1)
- So(inserted, ShouldBeTrue)
- })
- Convey("Walking the folder with dashboards", func() {
- noFiles := map[string]os.FileInfo{}
- Convey("should skip dirs that starts with .", func() {
- shouldSkip := createWalkFn(noFiles)("path", &FakeFileInfo{isDirectory: true, name: ".folder"}, nil)
- So(shouldSkip, ShouldEqual, filepath.SkipDir)
- })
- Convey("should keep walking if file is not .json", func() {
- shouldSkip := createWalkFn(noFiles)("path", &FakeFileInfo{isDirectory: true, name: "folder"}, nil)
- So(shouldSkip, ShouldBeNil)
- })
- })
- Convey("Given missing dashboard file", func() {
- cfg := &DashboardsAsConfig{
- Name: "Default",
- Type: "file",
- OrgId: 1,
- Options: map[string]interface{}{
- "folder": unprovision,
- },
- }
- fakeService.inserted = []*dashboards.SaveDashboardDTO{
- {Dashboard: &models.Dashboard{Id: 1}},
- {Dashboard: &models.Dashboard{Id: 2}},
- }
- absPath1, err := filepath.Abs(unprovision + "/dashboard1.json")
- So(err, ShouldBeNil)
- // This one does not exist on disc, simulating a deleted file
- absPath2, err := filepath.Abs(unprovision + "/dashboard2.json")
- So(err, ShouldBeNil)
- fakeService.provisioned = map[string][]*models.DashboardProvisioning{
- "Default": {
- {DashboardId: 1, Name: "Default", ExternalId: absPath1},
- {DashboardId: 2, Name: "Default", ExternalId: absPath2},
- },
- }
- Convey("Missing dashboard should be unprovisioned if DisableDeletion = true", func() {
- cfg.DisableDeletion = true
- reader, err := NewDashboardFileReader(cfg, logger)
- So(err, ShouldBeNil)
- err = reader.startWalkingDisk()
- So(err, ShouldBeNil)
- So(len(fakeService.provisioned["Default"]), ShouldEqual, 1)
- So(fakeService.provisioned["Default"][0].ExternalId, ShouldEqual, absPath1)
- })
- Convey("Missing dashboard should be deleted if DisableDeletion = false", func() {
- reader, err := NewDashboardFileReader(cfg, logger)
- So(err, ShouldBeNil)
- err = reader.startWalkingDisk()
- So(err, ShouldBeNil)
- So(len(fakeService.provisioned["Default"]), ShouldEqual, 1)
- So(fakeService.provisioned["Default"][0].ExternalId, ShouldEqual, absPath1)
- So(len(fakeService.inserted), ShouldEqual, 1)
- So(fakeService.inserted[0].Dashboard.Id, ShouldEqual, 1)
- })
- })
- Reset(func() {
- dashboards.NewProvisioningService = origNewDashboardProvisioningService
- })
- })
- }
- type FakeFileInfo struct {
- isDirectory bool
- name string
- }
- func (ffi *FakeFileInfo) IsDir() bool {
- return ffi.isDirectory
- }
- func (ffi FakeFileInfo) Size() int64 {
- return 1
- }
- func (ffi FakeFileInfo) Mode() os.FileMode {
- return 0777
- }
- func (ffi FakeFileInfo) Name() string {
- return ffi.name
- }
- func (ffi FakeFileInfo) ModTime() time.Time {
- return time.Time{}
- }
- func (ffi FakeFileInfo) Sys() interface{} {
- return nil
- }
- func mockDashboardProvisioningService() *fakeDashboardProvisioningService {
- mock := fakeDashboardProvisioningService{
- provisioned: map[string][]*models.DashboardProvisioning{},
- }
- dashboards.NewProvisioningService = func() dashboards.DashboardProvisioningService {
- return &mock
- }
- return &mock
- }
- type fakeDashboardProvisioningService struct {
- inserted []*dashboards.SaveDashboardDTO
- provisioned map[string][]*models.DashboardProvisioning
- getDashboard []*models.Dashboard
- }
- func (s *fakeDashboardProvisioningService) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
- if _, ok := s.provisioned[name]; !ok {
- s.provisioned[name] = []*models.DashboardProvisioning{}
- }
- return s.provisioned[name], nil
- }
- func (s *fakeDashboardProvisioningService) SaveProvisionedDashboard(dto *dashboards.SaveDashboardDTO, provisioning *models.DashboardProvisioning) (*models.Dashboard, error) {
- // Copy the structs as we need to change them but do not want to alter outside world.
- var copyProvisioning = &models.DashboardProvisioning{}
- *copyProvisioning = *provisioning
- var copyDto = &dashboards.SaveDashboardDTO{}
- *copyDto = *dto
- if copyDto.Dashboard.Id == 0 {
- copyDto.Dashboard.Id = rand.Int63n(1000000)
- } else {
- err := s.DeleteProvisionedDashboard(dto.Dashboard.Id, dto.Dashboard.OrgId)
- // Lets delete existing so we do not have duplicates
- if err != nil {
- return nil, err
- }
- }
- s.inserted = append(s.inserted, dto)
- if _, ok := s.provisioned[provisioning.Name]; !ok {
- s.provisioned[provisioning.Name] = []*models.DashboardProvisioning{}
- }
- for _, val := range s.provisioned[provisioning.Name] {
- if val.DashboardId == dto.Dashboard.Id && val.Name == provisioning.Name {
- // Do not insert duplicates
- return dto.Dashboard, nil
- }
- }
- copyProvisioning.DashboardId = copyDto.Dashboard.Id
- s.provisioned[provisioning.Name] = append(s.provisioned[provisioning.Name], copyProvisioning)
- return dto.Dashboard, nil
- }
- func (s *fakeDashboardProvisioningService) SaveFolderForProvisionedDashboards(dto *dashboards.SaveDashboardDTO) (*models.Dashboard, error) {
- s.inserted = append(s.inserted, dto)
- return dto.Dashboard, nil
- }
- func (s *fakeDashboardProvisioningService) UnprovisionDashboard(dashboardId int64) error {
- for key, val := range s.provisioned {
- for index, dashboard := range val {
- if dashboard.DashboardId == dashboardId {
- s.provisioned[key] = append(s.provisioned[key][:index], s.provisioned[key][index+1:]...)
- }
- }
- }
- return nil
- }
- func (s *fakeDashboardProvisioningService) DeleteProvisionedDashboard(dashboardId int64, orgId int64) error {
- err := s.UnprovisionDashboard(dashboardId)
- if err != nil {
- return err
- }
- for index, val := range s.inserted {
- if val.Dashboard.Id == dashboardId {
- s.inserted = append(s.inserted[:index], s.inserted[util.MinInt(index+1, len(s.inserted)):]...)
- }
- }
- return nil
- }
- func mockGetDashboardQuery(cmd *models.GetDashboardQuery) error {
- for _, d := range fakeService.getDashboard {
- if d.Slug == cmd.Slug {
- cmd.Result = d
- return nil
- }
- }
- return models.ErrDashboardNotFound
- }
|