mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-11-26 03:23:07 +01:00
Increase archive version. Fix unit tests.
This commit is contained in:
@@ -65,7 +65,7 @@ func setup(t *testing.T) *api.RestApi {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}`
|
}`
|
||||||
const testclusterJson = `{
|
const testclusterJSON = `{
|
||||||
"name": "testcluster",
|
"name": "testcluster",
|
||||||
"subClusters": [
|
"subClusters": [
|
||||||
{
|
{
|
||||||
@@ -128,7 +128,7 @@ func setup(t *testing.T) *api.RestApi {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(filepath.Join(jobarchive, "version.txt"), fmt.Appendf(nil, "%d", 2), 0o666); err != nil {
|
if err := os.WriteFile(filepath.Join(jobarchive, "version.txt"), fmt.Appendf(nil, "%d", 3), 0o666); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ func setup(t *testing.T) *api.RestApi {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(filepath.Join(jobarchive, "testcluster", "cluster.json"), []byte(testclusterJson), 0o666); err != nil {
|
if err := os.WriteFile(filepath.Join(jobarchive, "testcluster", "cluster.json"), []byte(testclusterJSON), 0o666); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// All rights reserved. This file is part of cc-backend.
|
// All rights reserved. This file is part of cc-backend.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -35,9 +36,9 @@ const (
|
|||||||
secondsPerDay = 86400
|
secondsPerDay = 86400
|
||||||
)
|
)
|
||||||
|
|
||||||
// StopJobApiRequest model
|
// StopJobAPIRequest model
|
||||||
type StopJobApiRequest struct {
|
type StopJobAPIRequest struct {
|
||||||
JobId *int64 `json:"jobId" example:"123000"`
|
JobID *int64 `json:"jobId" example:"123000"`
|
||||||
Cluster *string `json:"cluster" example:"fritz"`
|
Cluster *string `json:"cluster" example:"fritz"`
|
||||||
StartTime *int64 `json:"startTime" example:"1649723812"`
|
StartTime *int64 `json:"startTime" example:"1649723812"`
|
||||||
State schema.JobState `json:"jobState" validate:"required" example:"completed"`
|
State schema.JobState `json:"jobState" validate:"required" example:"completed"`
|
||||||
@@ -46,7 +47,7 @@ type StopJobApiRequest struct {
|
|||||||
|
|
||||||
// DeleteJobApiRequest model
|
// DeleteJobApiRequest model
|
||||||
type DeleteJobApiRequest struct {
|
type DeleteJobApiRequest struct {
|
||||||
JobId *int64 `json:"jobId" validate:"required" example:"123000"` // Cluster Job ID of job
|
JobID *int64 `json:"jobId" validate:"required" example:"123000"` // Cluster Job ID of job
|
||||||
Cluster *string `json:"cluster" example:"fritz"` // Cluster of job
|
Cluster *string `json:"cluster" example:"fritz"` // Cluster of job
|
||||||
StartTime *int64 `json:"startTime" example:"1649723812"` // Start Time of job as epoch
|
StartTime *int64 `json:"startTime" example:"1649723812"` // Start Time of job as epoch
|
||||||
}
|
}
|
||||||
@@ -740,7 +741,7 @@ func (api *RestApi) startJob(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// @router /api/jobs/stop_job/ [post]
|
// @router /api/jobs/stop_job/ [post]
|
||||||
func (api *RestApi) stopJobByRequest(rw http.ResponseWriter, r *http.Request) {
|
func (api *RestApi) stopJobByRequest(rw http.ResponseWriter, r *http.Request) {
|
||||||
// Parse request body
|
// Parse request body
|
||||||
req := StopJobApiRequest{}
|
req := StopJobAPIRequest{}
|
||||||
if err := decode(r.Body, &req); err != nil {
|
if err := decode(r.Body, &req); err != nil {
|
||||||
handleError(fmt.Errorf("parsing request body failed: %w", err), http.StatusBadRequest, rw)
|
handleError(fmt.Errorf("parsing request body failed: %w", err), http.StatusBadRequest, rw)
|
||||||
return
|
return
|
||||||
@@ -749,16 +750,16 @@ func (api *RestApi) stopJobByRequest(rw http.ResponseWriter, r *http.Request) {
|
|||||||
// Fetch job (that will be stopped) from db
|
// Fetch job (that will be stopped) from db
|
||||||
var job *schema.Job
|
var job *schema.Job
|
||||||
var err error
|
var err error
|
||||||
if req.JobId == nil {
|
if req.JobID == nil {
|
||||||
handleError(errors.New("the field 'jobId' is required"), http.StatusBadRequest, rw)
|
handleError(errors.New("the field 'jobId' is required"), http.StatusBadRequest, rw)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// cclog.Printf("loading db job for stopJobByRequest... : stopJobApiRequest=%v", req)
|
// cclog.Printf("loading db job for stopJobByRequest... : stopJobApiRequest=%v", req)
|
||||||
job, err = api.JobRepository.Find(req.JobId, req.Cluster, req.StartTime)
|
job, err = api.JobRepository.Find(req.JobID, req.Cluster, req.StartTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Try cached jobs if not found in main repository
|
// Try cached jobs if not found in main repository
|
||||||
cachedJob, cachedErr := api.JobRepository.FindCached(req.JobId, req.Cluster, req.StartTime)
|
cachedJob, cachedErr := api.JobRepository.FindCached(req.JobID, req.Cluster, req.StartTime)
|
||||||
if cachedErr != nil {
|
if cachedErr != nil {
|
||||||
// Combine both errors for better debugging
|
// Combine both errors for better debugging
|
||||||
handleError(fmt.Errorf("finding job failed: %w (cached lookup also failed: %v)", err, cachedErr), http.StatusNotFound, rw)
|
handleError(fmt.Errorf("finding job failed: %w (cached lookup also failed: %v)", err, cachedErr), http.StatusNotFound, rw)
|
||||||
@@ -841,12 +842,12 @@ func (api *RestApi) deleteJobByRequest(rw http.ResponseWriter, r *http.Request)
|
|||||||
// Fetch job (that will be deleted) from db
|
// Fetch job (that will be deleted) from db
|
||||||
var job *schema.Job
|
var job *schema.Job
|
||||||
var err error
|
var err error
|
||||||
if req.JobId == nil {
|
if req.JobID == nil {
|
||||||
handleError(errors.New("the field 'jobId' is required"), http.StatusBadRequest, rw)
|
handleError(errors.New("the field 'jobId' is required"), http.StatusBadRequest, rw)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
job, err = api.JobRepository.Find(req.JobId, req.Cluster, req.StartTime)
|
job, err = api.JobRepository.Find(req.JobID, req.Cluster, req.StartTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleError(fmt.Errorf("finding job failed: %w", err), http.StatusUnprocessableEntity, rw)
|
handleError(fmt.Errorf("finding job failed: %w", err), http.StatusUnprocessableEntity, rw)
|
||||||
return
|
return
|
||||||
@@ -913,7 +914,7 @@ func (api *RestApi) deleteJobBefore(rw http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *RestApi) checkAndHandleStopJob(rw http.ResponseWriter, job *schema.Job, req StopJobApiRequest) {
|
func (api *RestApi) checkAndHandleStopJob(rw http.ResponseWriter, job *schema.Job, req StopJobAPIRequest) {
|
||||||
// Sanity checks
|
// Sanity checks
|
||||||
if job.State != schema.JobStateRunning {
|
if job.State != schema.JobStateRunning {
|
||||||
handleError(fmt.Errorf("jobId %d (id %d) on %s : job has already been stopped (state is: %s)", job.JobID, job.ID, job.Cluster, job.State), http.StatusUnprocessableEntity, rw)
|
handleError(fmt.Errorf("jobId %d (id %d) on %s : job has already been stopped (state is: %s)", job.JobID, job.ID, job.Cluster, job.State), http.StatusUnprocessableEntity, rw)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// All rights reserved. This file is part of cc-backend.
|
// All rights reserved. This file is part of cc-backend.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -10,11 +11,12 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/repository"
|
"github.com/ClusterCockpit/cc-backend/internal/repository"
|
||||||
|
cclog "github.com/ClusterCockpit/cc-lib/ccLogger"
|
||||||
"github.com/ClusterCockpit/cc-lib/schema"
|
"github.com/ClusterCockpit/cc-lib/schema"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ApiReturnedUser struct {
|
type APIReturnedUser struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Roles []string `json:"roles"`
|
Roles []string `json:"roles"`
|
||||||
|
|||||||
@@ -81,14 +81,14 @@ func setup(t *testing.T) *repository.JobRepository {
|
|||||||
tmpdir := t.TempDir()
|
tmpdir := t.TempDir()
|
||||||
|
|
||||||
jobarchive := filepath.Join(tmpdir, "job-archive")
|
jobarchive := filepath.Join(tmpdir, "job-archive")
|
||||||
if err := os.Mkdir(jobarchive, 0777); err != nil {
|
if err := os.Mkdir(jobarchive, 0o777); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(filepath.Join(jobarchive, "version.txt"), []byte(fmt.Sprintf("%d", 2)), 0666); err != nil {
|
if err := os.WriteFile(filepath.Join(jobarchive, "version.txt"), fmt.Appendf(nil, "%d", 3), 0o666); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
fritzArchive := filepath.Join(tmpdir, "job-archive", "fritz")
|
fritzArchive := filepath.Join(tmpdir, "job-archive", "fritz")
|
||||||
if err := os.Mkdir(fritzArchive, 0777); err != nil {
|
if err := os.Mkdir(fritzArchive, 0o777); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := copyFile(filepath.Join("testdata", "cluster-fritz.json"),
|
if err := copyFile(filepath.Join("testdata", "cluster-fritz.json"),
|
||||||
@@ -103,7 +103,7 @@ func setup(t *testing.T) *repository.JobRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cfgFilePath := filepath.Join(tmpdir, "config.json")
|
cfgFilePath := filepath.Join(tmpdir, "config.json")
|
||||||
if err := os.WriteFile(cfgFilePath, []byte(testconfig), 0666); err != nil {
|
if err := os.WriteFile(cfgFilePath, []byte(testconfig), 0o666); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// All rights reserved. This file is part of cc-backend.
|
// All rights reserved. This file is part of cc-backend.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// All rights reserved. This file is part of cc-backend.
|
// All rights reserved. This file is part of cc-backend.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -35,15 +36,15 @@ type DatabaseOptions struct {
|
|||||||
ConnectionMaxIdleTime time.Duration
|
ConnectionMaxIdleTime time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupSqlite(db *sql.DB) (err error) {
|
func setupSqlite(db *sql.DB) error {
|
||||||
pragmas := []string{
|
pragmas := []string{
|
||||||
"temp_store = memory",
|
"temp_store = memory",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pragma := range pragmas {
|
for _, pragma := range pragmas {
|
||||||
_, err = db.Exec("PRAGMA " + pragma)
|
_, err := db.Exec("PRAGMA " + pragma)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,14 +68,14 @@ func Connect(driver string, db string) {
|
|||||||
case "sqlite3":
|
case "sqlite3":
|
||||||
// TODO: Have separate DB handles for Writes and Reads
|
// TODO: Have separate DB handles for Writes and Reads
|
||||||
// Optimize SQLite connection: https://kerkour.com/sqlite-for-servers
|
// Optimize SQLite connection: https://kerkour.com/sqlite-for-servers
|
||||||
connectionUrlParams := make(url.Values)
|
connectionURLParams := make(url.Values)
|
||||||
connectionUrlParams.Add("_txlock", "immediate")
|
connectionURLParams.Add("_txlock", "immediate")
|
||||||
connectionUrlParams.Add("_journal_mode", "WAL")
|
connectionURLParams.Add("_journal_mode", "WAL")
|
||||||
connectionUrlParams.Add("_busy_timeout", "5000")
|
connectionURLParams.Add("_busy_timeout", "5000")
|
||||||
connectionUrlParams.Add("_synchronous", "NORMAL")
|
connectionURLParams.Add("_synchronous", "NORMAL")
|
||||||
connectionUrlParams.Add("_cache_size", "1000000000")
|
connectionURLParams.Add("_cache_size", "1000000000")
|
||||||
connectionUrlParams.Add("_foreign_keys", "true")
|
connectionURLParams.Add("_foreign_keys", "true")
|
||||||
opts.URL = fmt.Sprintf("file:%s?%s", opts.URL, connectionUrlParams.Encode())
|
opts.URL = fmt.Sprintf("file:%s?%s", opts.URL, connectionURLParams.Encode())
|
||||||
|
|
||||||
if cclog.Loglevel() == "debug" {
|
if cclog.Loglevel() == "debug" {
|
||||||
sql.Register("sqlite3WithHooks", sqlhooks.Wrap(&sqlite3.SQLiteDriver{}, &Hooks{}))
|
sql.Register("sqlite3WithHooks", sqlhooks.Wrap(&sqlite3.SQLiteDriver{}, &Hooks{}))
|
||||||
@@ -83,7 +84,10 @@ func Connect(driver string, db string) {
|
|||||||
dbHandle, err = sqlx.Open("sqlite3", opts.URL)
|
dbHandle, err = sqlx.Open("sqlite3", opts.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
setupSqlite(dbHandle.DB)
|
err = setupSqlite(dbHandle.DB)
|
||||||
|
if err != nil {
|
||||||
|
cclog.Abortf("Failed sqlite db setup.\nError: %s\n", err.Error())
|
||||||
|
}
|
||||||
case "mysql":
|
case "mysql":
|
||||||
opts.URL += "?multiStatements=true"
|
opts.URL += "?multiStatements=true"
|
||||||
dbHandle, err = sqlx.Open("mysql", opts.URL)
|
dbHandle, err = sqlx.Open("mysql", opts.URL)
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ func nodeTestSetup(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(filepath.Join(jobarchive, "version.txt"),
|
if err := os.WriteFile(filepath.Join(jobarchive, "version.txt"),
|
||||||
fmt.Appendf(nil, "%d", 2), 0o666); err != nil {
|
fmt.Appendf(nil, "%d", 3), 0o666); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ import (
|
|||||||
|
|
||||||
// Version is the current archive schema version.
|
// Version is the current archive schema version.
|
||||||
// The archive backend must match this version for compatibility.
|
// The archive backend must match this version for compatibility.
|
||||||
const Version uint64 = 2
|
const Version uint64 = 3
|
||||||
|
|
||||||
// ArchiveBackend defines the interface that all archive storage backends must implement.
|
// ArchiveBackend defines the interface that all archive storage backends must implement.
|
||||||
// Implementations include FsArchive (filesystem), S3Archive (object storage), and SqliteArchive (database).
|
// Implementations include FsArchive (filesystem), S3Archive (object storage), and SqliteArchive (database).
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ func TestInit(t *testing.T) {
|
|||||||
if fsa.path != "testdata/archive" {
|
if fsa.path != "testdata/archive" {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
if version != 2 {
|
if version != 3 {
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
if len(fsa.clusters) != 3 || fsa.clusters[1] != "emmy" {
|
if len(fsa.clusters) != 3 || fsa.clusters[1] != "emmy" {
|
||||||
|
|||||||
2
pkg/archive/testdata/archive/version.txt
vendored
2
pkg/archive/testdata/archive/version.txt
vendored
@@ -1 +1 @@
|
|||||||
2
|
3
|
||||||
|
|||||||
Reference in New Issue
Block a user