github.com/filecoin-project/bacalhau@v0.3.23-0.20230228154132-45c989550ace/pkg/localdb/shared/test_utils.go (about)

     1  //nolint:all
     2  package shared
     3  
     4  import (
     5  	"context"
     6  	"database/sql"
     7  	"fmt"
     8  	"log"
     9  	"time"
    10  
    11  	"github.com/filecoin-project/bacalhau/pkg/localdb"
    12  	"github.com/filecoin-project/bacalhau/pkg/logger"
    13  	_ "github.com/filecoin-project/bacalhau/pkg/logger"
    14  	model "github.com/filecoin-project/bacalhau/pkg/model/v1beta1"
    15  	"github.com/filecoin-project/bacalhau/pkg/system"
    16  	"github.com/stretchr/testify/require"
    17  	"github.com/stretchr/testify/suite"
    18  )
    19  
    20  type GenericSQLSuite struct {
    21  	suite.Suite
    22  	SetupHandler    func() *GenericSQLDatastore
    23  	TeardownHandler func()
    24  	datastore       *GenericSQLDatastore
    25  	db              *sql.DB
    26  }
    27  
    28  func (suite *GenericSQLSuite) SetupTest() {
    29  	suite.Require().NoError(system.InitConfigForTesting(suite.T()))
    30  	logger.ConfigureTestLogging(suite.T())
    31  	datastore := suite.SetupHandler()
    32  	suite.datastore = datastore
    33  	suite.db = datastore.GetDB()
    34  }
    35  
    36  func (suite *GenericSQLSuite) TearDownSuite() {
    37  	if suite.TeardownHandler != nil {
    38  		suite.TeardownHandler()
    39  	}
    40  }
    41  
    42  func (suite *GenericSQLSuite) TestSQLiteMigrations() {
    43  	_, err := suite.db.Exec(`
    44  insert into job (id) values ('123');
    45  `)
    46  	require.NoError(suite.T(), err)
    47  	var id string
    48  	rows, err := suite.db.Query(`
    49  select id from job;
    50  `)
    51  	require.NoError(suite.T(), err)
    52  	defer rows.Close()
    53  	for rows.Next() {
    54  		err = rows.Scan(&id)
    55  		if err != nil {
    56  			log.Fatal(err)
    57  		}
    58  		log.Println(id)
    59  	}
    60  	err = rows.Err()
    61  	require.NoError(suite.T(), err)
    62  	require.Equal(suite.T(), id, "123")
    63  }
    64  
    65  func (suite *GenericSQLSuite) TestRoundtripJob() {
    66  	job := &model.Job{
    67  		Metadata: model.Metadata{
    68  			ID: "hellojob",
    69  		},
    70  	}
    71  	err := suite.datastore.AddJob(context.Background(), job)
    72  	require.NoError(suite.T(), err)
    73  	loadedJob, err := suite.datastore.GetJob(context.Background(), job.Metadata.ID)
    74  	require.NoError(suite.T(), err)
    75  	require.Equal(suite.T(), job.Metadata.ID, loadedJob.Metadata.ID)
    76  }
    77  
    78  func (suite *GenericSQLSuite) TestAddingTwoJobs() {
    79  	err := suite.datastore.AddJob(context.Background(), &model.Job{
    80  		Metadata: model.Metadata{
    81  			ID: "hellojob1",
    82  		},
    83  	})
    84  	require.NoError(suite.T(), err)
    85  	err = suite.datastore.AddJob(context.Background(), &model.Job{
    86  		Metadata: model.Metadata{
    87  			ID: "hellojob2",
    88  		},
    89  	})
    90  	require.NoError(suite.T(), err)
    91  }
    92  
    93  //nolint:funlen
    94  func (suite *GenericSQLSuite) TestGetJobs() {
    95  	jobCount := 100
    96  	dateString := "2021-11-22"
    97  	date, err := time.Parse("2006-01-02", dateString)
    98  	require.NoError(suite.T(), err)
    99  
   100  	for i := 0; i < jobCount; i++ {
   101  		date = date.Add(time.Hour * 1)
   102  		annotations := []string{"apples", fmt.Sprintf("oranges%d", i)}
   103  		if i < jobCount/2 {
   104  			annotations = append(annotations, "bananas")
   105  		}
   106  		job := &model.Job{
   107  			Metadata: model.Metadata{
   108  				ID:        fmt.Sprintf("hellojob%d", i),
   109  				CreatedAt: date,
   110  				ClientID:  fmt.Sprintf("testclient%d", i),
   111  			},
   112  			Spec: model.Spec{
   113  				Annotations: annotations,
   114  			},
   115  		}
   116  		err = suite.datastore.AddJob(context.Background(), job)
   117  		require.NoError(suite.T(), err)
   118  	}
   119  
   120  	// sanity check that we can see all jobs
   121  	allJobs, err := suite.datastore.GetJobs(context.Background(), localdb.JobQuery{})
   122  	require.NoError(suite.T(), err)
   123  	require.Equal(suite.T(), jobCount, len(allJobs))
   124  
   125  	// sort by date asc and check first id
   126  	sortedAsc, err := suite.datastore.GetJobs(context.Background(), localdb.JobQuery{
   127  		SortBy:      "created_at",
   128  		SortReverse: false,
   129  	})
   130  	require.NoError(suite.T(), err)
   131  	require.Equal(suite.T(), jobCount, len(sortedAsc))
   132  	require.Equal(suite.T(), "hellojob0", sortedAsc[0].Metadata.ID)
   133  
   134  	// sort by date desc and check first id
   135  	sortedDesc, err := suite.datastore.GetJobs(context.Background(), localdb.JobQuery{
   136  		SortBy:      "created_at",
   137  		SortReverse: true,
   138  	})
   139  	require.NoError(suite.T(), err)
   140  	require.Equal(suite.T(), jobCount, len(sortedDesc))
   141  	require.Equal(suite.T(), "hellojob99", sortedDesc[0].Metadata.ID)
   142  
   143  	// check basic limit
   144  	const limit = 10
   145  	tenJobs, err := suite.datastore.GetJobs(context.Background(), localdb.JobQuery{
   146  		Limit: limit,
   147  	})
   148  	require.NoError(suite.T(), err)
   149  	require.Equal(suite.T(), limit, len(tenJobs))
   150  
   151  	// pagination
   152  	tenJobsSecondPage, err := suite.datastore.GetJobs(context.Background(), localdb.JobQuery{
   153  		Limit:  limit,
   154  		Offset: 10,
   155  	})
   156  	require.NoError(suite.T(), err)
   157  	require.Equal(suite.T(), limit, len(tenJobsSecondPage))
   158  	require.Equal(suite.T(), "hellojob10", tenJobsSecondPage[0].Metadata.ID)
   159  
   160  	loadedJobCount, err := suite.datastore.GetJobsCount(context.Background(), localdb.JobQuery{
   161  		IncludeTags: []model.IncludedTag{"bananas"},
   162  		Limit:       1,
   163  		SortBy:      "created_at",
   164  		SortReverse: true,
   165  	})
   166  	require.NoError(suite.T(), err)
   167  	require.Equal(suite.T(), jobCount/2, loadedJobCount)
   168  
   169  	sortedWithLimit, err := suite.datastore.GetJobs(context.Background(), localdb.JobQuery{
   170  		SortBy:      "created_at",
   171  		SortReverse: true,
   172  		Limit:       limit,
   173  	})
   174  	require.NoError(suite.T(), err)
   175  	require.Equal(suite.T(), limit, len(sortedWithLimit))
   176  
   177  	// a label they all have
   178  	withAppleLabel, err := suite.datastore.GetJobs(context.Background(), localdb.JobQuery{
   179  		IncludeTags: []model.IncludedTag{"apples"},
   180  	})
   181  	require.NoError(suite.T(), err)
   182  	require.Equal(suite.T(), jobCount, len(withAppleLabel))
   183  
   184  	// a label only half of them have
   185  	withBananasLabel, err := suite.datastore.GetJobs(context.Background(), localdb.JobQuery{
   186  		IncludeTags: []model.IncludedTag{"bananas"},
   187  	})
   188  	require.NoError(suite.T(), err)
   189  	require.Equal(suite.T(), jobCount/2, len(withBananasLabel))
   190  
   191  	// a label only half of them have plus a second label
   192  	withAppleAndBananaLabel, err := suite.datastore.GetJobs(context.Background(), localdb.JobQuery{
   193  		IncludeTags: []model.IncludedTag{"apples", "bananas"},
   194  	})
   195  	require.NoError(suite.T(), err)
   196  	require.Equal(suite.T(), jobCount/2, len(withAppleAndBananaLabel))
   197  
   198  	// combine three labels - only 1 result
   199  	withAppleAndBananaAndIDLabel, err := suite.datastore.GetJobs(context.Background(), localdb.JobQuery{
   200  		IncludeTags: []model.IncludedTag{"apples", "bananas", "oranges17"},
   201  	})
   202  	require.NoError(suite.T(), err)
   203  	require.Equal(suite.T(), 1, len(withAppleAndBananaAndIDLabel))
   204  
   205  	// exclude with a single id
   206  	basicExclude, err := suite.datastore.GetJobs(context.Background(), localdb.JobQuery{
   207  		ExcludeTags: []model.ExcludedTag{"oranges17"},
   208  	})
   209  	require.NoError(suite.T(), err)
   210  	require.Equal(suite.T(), jobCount-1, len(basicExclude))
   211  
   212  	// exclude half
   213  	halfExclude, err := suite.datastore.GetJobs(context.Background(), localdb.JobQuery{
   214  		ExcludeTags: []model.ExcludedTag{"bananas"},
   215  	})
   216  	require.NoError(suite.T(), err)
   217  	require.Equal(suite.T(), jobCount/2, len(halfExclude))
   218  
   219  	// include and exclude
   220  	includeAndExclude, err := suite.datastore.GetJobs(context.Background(), localdb.JobQuery{
   221  		IncludeTags: []model.IncludedTag{"apples", "bananas"},
   222  		ExcludeTags: []model.ExcludedTag{"oranges17"},
   223  	})
   224  	require.NoError(suite.T(), err)
   225  	require.Equal(suite.T(), jobCount/2-1, len(includeAndExclude))
   226  
   227  	// load jobs from one client
   228  	singleClient, err := suite.datastore.GetJobs(context.Background(), localdb.JobQuery{
   229  		ClientID: "testclient17",
   230  	})
   231  	require.NoError(suite.T(), err)
   232  	require.Equal(suite.T(), 1, len(singleClient))
   233  }
   234  
   235  func (suite *GenericSQLSuite) TestJobEvents() {
   236  	eventCount := 5
   237  	job := &model.Job{
   238  		Metadata: model.Metadata{
   239  			ID: "hellojob",
   240  		},
   241  	}
   242  	err := suite.datastore.AddJob(context.Background(), job)
   243  	require.NoError(suite.T(), err)
   244  
   245  	dateString := "2021-11-22"
   246  	date, err := time.Parse("2006-01-02", dateString)
   247  	require.NoError(suite.T(), err)
   248  
   249  	for i := 0; i < eventCount; i++ {
   250  		date = date.Add(time.Minute * 1)
   251  		eventName := model.JobEventBid
   252  		localEventName := model.JobLocalEventBid
   253  		if i == 0 {
   254  			eventName = model.JobEventCreated
   255  			localEventName = model.JobLocalEventSelected
   256  		}
   257  		ev := model.JobEvent{
   258  			JobID:     job.Metadata.ID,
   259  			EventName: eventName,
   260  			EventTime: date,
   261  		}
   262  		err = suite.datastore.AddEvent(context.Background(), job.Metadata.ID, ev)
   263  		require.NoError(suite.T(), err)
   264  		localEvent := model.JobLocalEvent{
   265  			JobID:     job.Metadata.ID,
   266  			EventName: localEventName,
   267  		}
   268  		err = suite.datastore.AddLocalEvent(context.Background(), job.Metadata.ID, localEvent)
   269  		require.NoError(suite.T(), err)
   270  	}
   271  
   272  	jobEvents, err := suite.datastore.GetJobEvents(context.Background(), job.Metadata.ID)
   273  	require.NoError(suite.T(), err)
   274  	require.Equal(suite.T(), eventCount, len(jobEvents))
   275  	// check that the EventName of the first event is the created one
   276  	require.Equal(suite.T(), model.JobEventCreated, jobEvents[0].EventName)
   277  
   278  	// repeat the same event block above but for local events
   279  	localEvents, err := suite.datastore.GetJobLocalEvents(context.Background(), job.Metadata.ID)
   280  	require.NoError(suite.T(), err)
   281  	require.Equal(suite.T(), eventCount, len(localEvents))
   282  	// check that the EventName of the first event is the created one
   283  	require.Equal(suite.T(), model.JobLocalEventSelected, localEvents[0].EventName)
   284  }
   285  
   286  func (suite *GenericSQLSuite) TestJobState() {
   287  	job := &model.Job{
   288  		Metadata: model.Metadata{
   289  			ID: "hellojob",
   290  		},
   291  	}
   292  	err := suite.datastore.AddJob(context.Background(), job)
   293  	require.NoError(suite.T(), err)
   294  	err = suite.datastore.UpdateShardState(
   295  		context.Background(),
   296  		job.Metadata.ID,
   297  		"node-test",
   298  		0,
   299  		model.JobShardState{
   300  			NodeID:     "node-test",
   301  			ShardIndex: 0,
   302  			State:      model.JobStateRunning,
   303  		},
   304  	)
   305  	require.NoError(suite.T(), err)
   306  	state, err := suite.datastore.GetJobState(context.Background(), job.Metadata.ID)
   307  	require.NoError(suite.T(), err)
   308  	node, ok := state.Nodes["node-test"]
   309  	require.True(suite.T(), ok)
   310  	shard, ok := node.Shards[0]
   311  	require.True(suite.T(), ok)
   312  	require.Equal(suite.T(), "node-test", shard.NodeID)
   313  	require.Equal(suite.T(), model.JobStateRunning, shard.State)
   314  }
   315  
   316  func (suite *GenericSQLSuite) TestUpdateDeal() {
   317  	job := &model.Job{
   318  		Metadata: model.Metadata{
   319  			ID: "hellojob",
   320  		},
   321  		Spec: model.Spec{
   322  			Deal: model.Deal{
   323  				Concurrency: 1,
   324  			},
   325  		},
   326  	}
   327  	err := suite.datastore.AddJob(context.Background(), job)
   328  	require.NoError(suite.T(), err)
   329  	err = suite.datastore.UpdateJobDeal(
   330  		context.Background(),
   331  		job.Metadata.ID,
   332  		model.Deal{
   333  			Concurrency: 3,
   334  		},
   335  	)
   336  	require.NoError(suite.T(), err)
   337  	updatedJob, err := suite.datastore.GetJob(context.Background(), job.Metadata.ID)
   338  	require.NoError(suite.T(), err)
   339  	require.Equal(suite.T(), 3, updatedJob.Spec.Deal.Concurrency)
   340  }