Storage configuration support [storage] (#13314) (#13379)

* Fix minio bug

* Add tests for storage configuration

* Change the Seek flag to keep compitable minio?

* Fix test when first-byte-pos of all ranges is greater than the resource length

Co-authored-by: techknowlogick <techknowlogick@gitea.io>

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
This commit is contained in:
Lunny Xiao 2020-11-01 23:12:50 +08:00 committed by GitHub
parent c3e752ae29
commit 02259a0f3a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 62 additions and 33 deletions

View file

@ -11,7 +11,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"net/http/cookiejar" "net/http/cookiejar"
"net/http/httptest" "net/http/httptest"
@ -27,8 +26,10 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers"
"code.gitea.io/gitea/routers/routes" "code.gitea.io/gitea/routers/routes"
@ -59,6 +60,8 @@ func NewNilResponseRecorder() *NilResponseRecorder {
} }
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
defer log.Close()
managerCtx, cancel := context.WithCancel(context.Background()) managerCtx, cancel := context.WithCancel(context.Background())
graceful.InitManager(managerCtx) graceful.InitManager(managerCtx)
defer cancel() defer cancel()
@ -142,6 +145,10 @@ func initIntegrationTest() {
util.RemoveAll(models.LocalCopyPath()) util.RemoveAll(models.LocalCopyPath())
setting.CheckLFSVersion() setting.CheckLFSVersion()
setting.InitDBConfig() setting.InitDBConfig()
if err := storage.Init(); err != nil {
fmt.Printf("Init storage failed: %v", err)
os.Exit(1)
}
switch { switch {
case setting.Database.UseMySQL: case setting.Database.UseMySQL:
@ -149,27 +156,27 @@ func initIntegrationTest() {
setting.Database.User, setting.Database.Passwd, setting.Database.Host)) setting.Database.User, setting.Database.Passwd, setting.Database.Host))
defer db.Close() defer db.Close()
if err != nil { if err != nil {
log.Fatalf("sql.Open: %v", err) log.Fatal("sql.Open: %v", err)
} }
if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", setting.Database.Name)); err != nil { if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", setting.Database.Name)); err != nil {
log.Fatalf("db.Exec: %v", err) log.Fatal("db.Exec: %v", err)
} }
case setting.Database.UsePostgreSQL: case setting.Database.UsePostgreSQL:
db, err := sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/?sslmode=%s", db, err := sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/?sslmode=%s",
setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.SSLMode)) setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.SSLMode))
defer db.Close() defer db.Close()
if err != nil { if err != nil {
log.Fatalf("sql.Open: %v", err) log.Fatal("sql.Open: %v", err)
} }
dbrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM pg_database WHERE datname = '%s'", setting.Database.Name)) dbrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM pg_database WHERE datname = '%s'", setting.Database.Name))
if err != nil { if err != nil {
log.Fatalf("db.Query: %v", err) log.Fatal("db.Query: %v", err)
} }
defer dbrows.Close() defer dbrows.Close()
if !dbrows.Next() { if !dbrows.Next() {
if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", setting.Database.Name)); err != nil { if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", setting.Database.Name)); err != nil {
log.Fatalf("db.Exec: CREATE DATABASE: %v", err) log.Fatal("db.Exec: CREATE DATABASE: %v", err)
} }
} }
// Check if we need to setup a specific schema // Check if we need to setup a specific schema
@ -183,18 +190,18 @@ func initIntegrationTest() {
// This is a different db object; requires a different Close() // This is a different db object; requires a different Close()
defer db.Close() defer db.Close()
if err != nil { if err != nil {
log.Fatalf("sql.Open: %v", err) log.Fatal("sql.Open: %v", err)
} }
schrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema)) schrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema))
if err != nil { if err != nil {
log.Fatalf("db.Query: %v", err) log.Fatal("db.Query: %v", err)
} }
defer schrows.Close() defer schrows.Close()
if !schrows.Next() { if !schrows.Next() {
// Create and setup a DB schema // Create and setup a DB schema
if _, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s", setting.Database.Schema)); err != nil { if _, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s", setting.Database.Schema)); err != nil {
log.Fatalf("db.Exec: CREATE SCHEMA: %v", err) log.Fatal("db.Exec: CREATE SCHEMA: %v", err)
} }
} }
@ -203,10 +210,10 @@ func initIntegrationTest() {
db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;",
host, port, "master", setting.Database.User, setting.Database.Passwd)) host, port, "master", setting.Database.User, setting.Database.Passwd))
if err != nil { if err != nil {
log.Fatalf("sql.Open: %v", err) log.Fatal("sql.Open: %v", err)
} }
if _, err := db.Exec(fmt.Sprintf("If(db_id(N'%s') IS NULL) BEGIN CREATE DATABASE %s; END;", setting.Database.Name, setting.Database.Name)); err != nil { if _, err := db.Exec(fmt.Sprintf("If(db_id(N'%s') IS NULL) BEGIN CREATE DATABASE %s; END;", setting.Database.Name, setting.Database.Name)); err != nil {
log.Fatalf("db.Exec: %v", err) log.Fatal("db.Exec: %v", err)
} }
defer db.Close() defer db.Close()
} }

View file

@ -78,6 +78,7 @@ func storeAndGetLfs(t *testing.T, content *[]byte, extraHeader *http.Header, exp
} }
} }
} }
resp := session.MakeRequest(t, req, expectedStatus) resp := session.MakeRequest(t, req, expectedStatus)
return resp return resp
@ -210,7 +211,7 @@ func TestGetLFSRange(t *testing.T) {
{"bytes=0-10", "123456789\n", http.StatusPartialContent}, {"bytes=0-10", "123456789\n", http.StatusPartialContent},
// end-range bigger than length-1 is ignored // end-range bigger than length-1 is ignored
{"bytes=0-11", "123456789\n", http.StatusPartialContent}, {"bytes=0-11", "123456789\n", http.StatusPartialContent},
{"bytes=11-", "", http.StatusPartialContent}, {"bytes=11-", "Requested Range Not Satisfiable", http.StatusRequestedRangeNotSatisfiable},
// incorrect header value cause whole header to be ignored // incorrect header value cause whole header to be ignored
{"bytes=-", "123456789\n", http.StatusOK}, {"bytes=-", "123456789\n", http.StatusOK},
{"foobar", "123456789\n", http.StatusOK}, {"foobar", "123456789\n", http.StatusOK},

View file

@ -45,19 +45,21 @@ START_SSH_SERVER = true
OFFLINE_MODE = false OFFLINE_MODE = false
LFS_START_SERVER = true LFS_START_SERVER = true
LFS_CONTENT_PATH = integrations/gitea-integration-mysql/datalfs-mysql
LFS_JWT_SECRET = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w LFS_JWT_SECRET = Tv_MjmZuHqpIY6GFl12ebgkRAMt4RlWt0v4EHKSXO0w
LFS_STORE_TYPE = minio
LFS_SERVE_DIRECT = false [lfs]
LFS_MINIO_ENDPOINT = minio:9000 MINIO_BASE_PATH = lfs/
LFS_MINIO_ACCESS_KEY_ID = 123456
LFS_MINIO_SECRET_ACCESS_KEY = 12345678
LFS_MINIO_BUCKET = gitea
LFS_MINIO_LOCATION = us-east-1
LFS_MINIO_BASE_PATH = lfs/
LFS_MINIO_USE_SSL = false
[attachment] [attachment]
MINIO_BASE_PATH = attachments/
[avatars]
MINIO_BASE_PATH = avatars/
[repo-avatars]
MINIO_BASE_PATH = repo-avatars/
[storage]
STORAGE_TYPE = minio STORAGE_TYPE = minio
SERVE_DIRECT = false SERVE_DIRECT = false
MINIO_ENDPOINT = minio:9000 MINIO_ENDPOINT = minio:9000
@ -65,7 +67,6 @@ MINIO_ACCESS_KEY_ID = 123456
MINIO_SECRET_ACCESS_KEY = 12345678 MINIO_SECRET_ACCESS_KEY = 12345678
MINIO_BUCKET = gitea MINIO_BUCKET = gitea
MINIO_LOCATION = us-east-1 MINIO_LOCATION = us-east-1
MINIO_BASE_PATH = attachments/
MINIO_USE_SSL = false MINIO_USE_SSL = false
[mailer] [mailer]
@ -88,9 +89,6 @@ ENABLE_NOTIFY_MAIL = true
DISABLE_GRAVATAR = false DISABLE_GRAVATAR = false
ENABLE_FEDERATED_AVATAR = false ENABLE_FEDERATED_AVATAR = false
AVATAR_UPLOAD_PATH = integrations/gitea-integration-mysql/data/avatars
REPOSITORY_AVATAR_UPLOAD_PATH = integrations/gitea-integration-mysql/data/repo-avatars
[session] [session]
PROVIDER = file PROVIDER = file
PROVIDER_CONFIG = integrations/gitea-integration-mysql/data/sessions PROVIDER_CONFIG = integrations/gitea-integration-mysql/data/sessions

View file

@ -8,6 +8,7 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt"
"io" "io"
"os" "os"
@ -21,6 +22,21 @@ var (
errSizeMismatch = errors.New("Content size does not match") errSizeMismatch = errors.New("Content size does not match")
) )
// ErrRangeNotSatisfiable represents an error which request range is not satisfiable.
type ErrRangeNotSatisfiable struct {
FromByte int64
}
func (err ErrRangeNotSatisfiable) Error() string {
return fmt.Sprintf("Requested range %d is not satisfiable", err.FromByte)
}
// IsErrRangeNotSatisfiable returns true if the error is an ErrRangeNotSatisfiable
func IsErrRangeNotSatisfiable(err error) bool {
_, ok := err.(ErrRangeNotSatisfiable)
return ok
}
// ContentStore provides a simple file system based storage. // ContentStore provides a simple file system based storage.
type ContentStore struct { type ContentStore struct {
storage.ObjectStorage storage.ObjectStorage
@ -35,7 +51,12 @@ func (s *ContentStore) Get(meta *models.LFSMetaObject, fromByte int64) (io.ReadC
return nil, err return nil, err
} }
if fromByte > 0 { if fromByte > 0 {
_, err = f.Seek(fromByte, os.SEEK_CUR) if fromByte >= meta.Size {
return nil, ErrRangeNotSatisfiable{
FromByte: fromByte,
}
}
_, err = f.Seek(fromByte, io.SeekStart)
if err != nil { if err != nil {
log.Error("Whilst trying to read LFS OID[%s]: Unable to seek to %d Error: %v", meta.Oid, fromByte, err) log.Error("Whilst trying to read LFS OID[%s]: Unable to seek to %d Error: %v", meta.Oid, fromByte, err)
} }

View file

@ -191,8 +191,12 @@ func getContentHandler(ctx *context.Context) {
contentStore := &ContentStore{ObjectStorage: storage.LFS} contentStore := &ContentStore{ObjectStorage: storage.LFS}
content, err := contentStore.Get(meta, fromByte) content, err := contentStore.Get(meta, fromByte)
if err != nil { if err != nil {
// Errors are logged in contentStore.Get if IsErrRangeNotSatisfiable(err) {
writeStatus(ctx, 404) writeStatus(ctx, http.StatusRequestedRangeNotSatisfiable)
} else {
// Errors are logged in contentStore.Get
writeStatus(ctx, 404)
}
return return
} }
defer content.Close() defer content.Close()

View file

@ -32,14 +32,12 @@ func (s *Storage) MapTo(v interface{}) error {
} }
func getStorage(name, typ string, overrides ...*ini.Section) Storage { func getStorage(name, typ string, overrides ...*ini.Section) Storage {
sectionName := "storage" const sectionName = "storage"
if len(name) > 0 {
sectionName = sectionName + "." + typ
}
sec := Cfg.Section(sectionName) sec := Cfg.Section(sectionName)
if len(overrides) == 0 { if len(overrides) == 0 {
overrides = []*ini.Section{ overrides = []*ini.Section{
Cfg.Section(sectionName + "." + typ),
Cfg.Section(sectionName + "." + name), Cfg.Section(sectionName + "." + name),
} }
} }