Skip to content

Commit 9269b7f

Browse files
zeripathlafriks
andauthored
Multiple LFS improvements (#10667)
* Add more logging in the LFS server Adds more logging in the LFS server and stops sending internal server error information to the client * Add LFS Lock cursor implementation * Simplify Claims in LFS and remove the float64 casts Signed-off-by: Andrew Thornton <[email protected]> Co-authored-by: Lauris BH <[email protected]>
1 parent 3fc4f36 commit 9269b7f

File tree

7 files changed

+165
-52
lines changed

7 files changed

+165
-52
lines changed

cmd/serv.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"time"
1919

2020
"code.gitea.io/gitea/models"
21+
"code.gitea.io/gitea/modules/lfs"
2122
"code.gitea.io/gitea/modules/log"
2223
"code.gitea.io/gitea/modules/pprof"
2324
"code.gitea.io/gitea/modules/private"
@@ -213,12 +214,14 @@ func runServ(c *cli.Context) error {
213214
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
214215

215216
now := time.Now()
216-
claims := jwt.MapClaims{
217-
"repo": results.RepoID,
218-
"op": lfsVerb,
219-
"exp": now.Add(setting.LFS.HTTPAuthExpiry).Unix(),
220-
"nbf": now.Unix(),
221-
"user": results.UserID,
217+
claims := lfs.Claims{
218+
StandardClaims: jwt.StandardClaims{
219+
ExpiresAt: now.Add(setting.LFS.HTTPAuthExpiry).Unix(),
220+
NotBefore: now.Unix(),
221+
},
222+
RepoID: results.RepoID,
223+
Op: lfsVerb,
224+
UserID: results.UserID,
222225
}
223226
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
224227

custom/conf/app.ini.sample

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@ LFS_JWT_SECRET =
313313
LFS_HTTP_AUTH_EXPIRY = 20m
314314
; Maximum allowed LFS file size in bytes (Set to 0 for no limit).
315315
LFS_MAX_FILE_SIZE = 0
316+
; Maximum number of locks returned per page
317+
LFS_LOCKS_PAGING_NUM = 50
316318
; Allow graceful restarts using SIGHUP to fork
317319
ALLOW_GRACEFUL_RESTARTS = true
318320
; After a restart the parent will finish ongoing requests before

docs/content/doc/advanced/config-cheat-sheet.en-us.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
193193
- `LFS_JWT_SECRET`: **\<empty\>**: LFS authentication secret, change this a unique string.
194194
- `LFS_HTTP_AUTH_EXPIRY`: **20m**: LFS authentication validity period in time.Duration, pushes taking longer than this may fail.
195195
- `LFS_MAX_FILE_SIZE`: **0**: Maximum allowed LFS file size in bytes (Set to 0 for no limit).
196+
- `LFS_LOCK_PAGING_NUM`: **50**: Maximum number of LFS Locks returned per page.
196197
- `REDIRECT_OTHER_PORT`: **false**: If true and `PROTOCOL` is https, allows redirecting http requests on `PORT_TO_REDIRECT` to the https port Gitea listens on.
197198
- `PORT_TO_REDIRECT`: **80**: Port for the http redirection service to listen on. Used when `REDIRECT_OTHER_PORT` is true.
198199
- `ENABLE_LETSENCRYPT`: **false**: If enabled you must set `DOMAIN` to valid internet facing domain (ensure DNS is set and port 80 is accessible by letsencrypt validation server).

modules/lfs/content_store.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"path/filepath"
1010

1111
"code.gitea.io/gitea/models"
12+
"code.gitea.io/gitea/modules/log"
1213
)
1314

1415
var (
@@ -28,10 +29,14 @@ func (s *ContentStore) Get(meta *models.LFSMetaObject, fromByte int64) (io.ReadC
2829

2930
f, err := os.Open(path)
3031
if err != nil {
32+
log.Error("Whilst trying to read LFS OID[%s]: Unable to open %s Error: %v", meta.Oid, path, err)
3133
return nil, err
3234
}
3335
if fromByte > 0 {
3436
_, err = f.Seek(fromByte, os.SEEK_CUR)
37+
if err != nil {
38+
log.Error("Whilst trying to read LFS OID[%s]: Unable to seek to %d Error: %v", meta.Oid, fromByte, err)
39+
}
3540
}
3641
return f, err
3742
}
@@ -43,11 +48,13 @@ func (s *ContentStore) Put(meta *models.LFSMetaObject, r io.Reader) error {
4348

4449
dir := filepath.Dir(path)
4550
if err := os.MkdirAll(dir, 0750); err != nil {
51+
log.Error("Whilst putting LFS OID[%s]: Unable to create the LFS directory: %s Error: %v", meta.Oid, dir, err)
4652
return err
4753
}
4854

4955
file, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0640)
5056
if err != nil {
57+
log.Error("Whilst putting LFS OID[%s]: Unable to open temporary file for writing: %s Error: %v", tmpPath, err)
5158
return err
5259
}
5360
defer os.Remove(tmpPath)
@@ -57,6 +64,7 @@ func (s *ContentStore) Put(meta *models.LFSMetaObject, r io.Reader) error {
5764

5865
written, err := io.Copy(hw, r)
5966
if err != nil {
67+
log.Error("Whilst putting LFS OID[%s]: Failed to copy to tmpPath: %s Error: %v", meta.Oid, tmpPath, err)
6068
file.Close()
6169
return err
6270
}
@@ -71,7 +79,12 @@ func (s *ContentStore) Put(meta *models.LFSMetaObject, r io.Reader) error {
7179
return errHashMismatch
7280
}
7381

74-
return os.Rename(tmpPath, path)
82+
if err := os.Rename(tmpPath, path); err != nil {
83+
log.Error("Whilst putting LFS OID[%s]: Unable to move tmp file to final destination: %s Error: %v", meta.Oid, path, err)
84+
return err
85+
}
86+
87+
return nil
7588
}
7689

7790
// Exists returns true if the object exists in the content store.
@@ -91,6 +104,7 @@ func (s *ContentStore) Verify(meta *models.LFSMetaObject) (bool, error) {
91104
if os.IsNotExist(err) || err == nil && fi.Size() != meta.Size {
92105
return false, nil
93106
} else if err != nil {
107+
log.Error("Unable stat file: %s for LFS OID[%s] Error: %v", path, meta.Oid, err)
94108
return false, err
95109
}
96110

modules/lfs/locks.go

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ import (
1919
//checkIsValidRequest check if it a valid request in case of bad request it write the response to ctx.
2020
func checkIsValidRequest(ctx *context.Context) bool {
2121
if !setting.LFS.StartServer {
22+
log.Debug("Attempt to access LFS server but LFS server is disabled")
2223
writeStatus(ctx, 404)
2324
return false
2425
}
2526
if !MetaMatcher(ctx.Req) {
27+
log.Info("Attempt access LOCKs without accepting the correct media type: %s", metaMediaType)
2628
writeStatus(ctx, 400)
2729
return false
2830
}
@@ -47,7 +49,7 @@ func handleLockListOut(ctx *context.Context, repo *models.Repository, lock *mode
4749
return
4850
}
4951
ctx.JSON(500, api.LFSLockError{
50-
Message: "unable to list locks : " + err.Error(),
52+
Message: "unable to list locks : Internal Server Error",
5153
})
5254
return
5355
}
@@ -65,6 +67,7 @@ func handleLockListOut(ctx *context.Context, repo *models.Repository, lock *mode
6567
// GetListLockHandler list locks
6668
func GetListLockHandler(ctx *context.Context) {
6769
if !checkIsValidRequest(ctx) {
70+
// Status is written in checkIsValidRequest
6871
return
6972
}
7073
ctx.Resp.Header().Set("Content-Type", metaMediaType)
@@ -87,7 +90,17 @@ func GetListLockHandler(ctx *context.Context) {
8790
})
8891
return
8992
}
90-
//TODO handle query cursor and limit
93+
94+
cursor := ctx.QueryInt("cursor")
95+
if cursor < 0 {
96+
cursor = 0
97+
}
98+
limit := ctx.QueryInt("limit")
99+
if limit > setting.LFS.LocksPagingNum && setting.LFS.LocksPagingNum > 0 {
100+
limit = setting.LFS.LocksPagingNum
101+
} else if limit < 0 {
102+
limit = 0
103+
}
91104
id := ctx.Query("id")
92105
if id != "" { //Case where we request a specific id
93106
v, err := strconv.ParseInt(id, 10, 64)
@@ -98,37 +111,50 @@ func GetListLockHandler(ctx *context.Context) {
98111
return
99112
}
100113
lock, err := models.GetLFSLockByID(v)
114+
if err != nil && !models.IsErrLFSLockNotExist(err) {
115+
log.Error("Unable to get lock with ID[%s]: Error: %v", v, err)
116+
}
101117
handleLockListOut(ctx, repository, lock, err)
102118
return
103119
}
104120

105121
path := ctx.Query("path")
106122
if path != "" { //Case where we request a specific id
107123
lock, err := models.GetLFSLock(repository, path)
124+
if err != nil && !models.IsErrLFSLockNotExist(err) {
125+
log.Error("Unable to get lock for repository %-v with path %s: Error: %v", repository, path, err)
126+
}
108127
handleLockListOut(ctx, repository, lock, err)
109128
return
110129
}
111130

112131
//If no query params path or id
113-
lockList, err := models.GetLFSLockByRepoID(repository.ID, 0, 0)
132+
lockList, err := models.GetLFSLockByRepoID(repository.ID, cursor, limit)
114133
if err != nil {
134+
log.Error("Unable to list locks for repository ID[%d]: Error: %v", repository.ID, err)
115135
ctx.JSON(500, api.LFSLockError{
116-
Message: "unable to list locks : " + err.Error(),
136+
Message: "unable to list locks : Internal Server Error",
117137
})
118138
return
119139
}
120140
lockListAPI := make([]*api.LFSLock, len(lockList))
141+
next := ""
121142
for i, l := range lockList {
122143
lockListAPI[i] = l.APIFormat()
123144
}
145+
if limit > 0 && len(lockList) == limit {
146+
next = strconv.Itoa(cursor + 1)
147+
}
124148
ctx.JSON(200, api.LFSLockList{
125149
Locks: lockListAPI,
150+
Next: next,
126151
})
127152
}
128153

129154
// PostLockHandler create lock
130155
func PostLockHandler(ctx *context.Context) {
131156
if !checkIsValidRequest(ctx) {
157+
// Status is written in checkIsValidRequest
132158
return
133159
}
134160
ctx.Resp.Header().Set("Content-Type", metaMediaType)
@@ -139,7 +165,7 @@ func PostLockHandler(ctx *context.Context) {
139165

140166
repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
141167
if err != nil {
142-
log.Debug("Could not find repository: %s/%s - %s", userName, repoName, err)
168+
log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err)
143169
writeStatus(ctx, 404)
144170
return
145171
}
@@ -159,6 +185,7 @@ func PostLockHandler(ctx *context.Context) {
159185
defer bodyReader.Close()
160186
dec := json.NewDecoder(bodyReader)
161187
if err := dec.Decode(&req); err != nil {
188+
log.Warn("Failed to decode lock request as json. Error: %v", err)
162189
writeStatus(ctx, 400)
163190
return
164191
}
@@ -183,8 +210,9 @@ func PostLockHandler(ctx *context.Context) {
183210
})
184211
return
185212
}
213+
log.Error("Unable to CreateLFSLock in repository %-v at %s for user %-v: Error: %v", repository, req.Path, ctx.User, err)
186214
ctx.JSON(500, api.LFSLockError{
187-
Message: "internal server error : " + err.Error(),
215+
Message: "internal server error : Internal Server Error",
188216
})
189217
return
190218
}
@@ -194,6 +222,7 @@ func PostLockHandler(ctx *context.Context) {
194222
// VerifyLockHandler list locks for verification
195223
func VerifyLockHandler(ctx *context.Context) {
196224
if !checkIsValidRequest(ctx) {
225+
// Status is written in checkIsValidRequest
197226
return
198227
}
199228
ctx.Resp.Header().Set("Content-Type", metaMediaType)
@@ -204,7 +233,7 @@ func VerifyLockHandler(ctx *context.Context) {
204233

205234
repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
206235
if err != nil {
207-
log.Debug("Could not find repository: %s/%s - %s", userName, repoName, err)
236+
log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err)
208237
writeStatus(ctx, 404)
209238
return
210239
}
@@ -219,14 +248,28 @@ func VerifyLockHandler(ctx *context.Context) {
219248
return
220249
}
221250

222-
//TODO handle body json cursor and limit
223-
lockList, err := models.GetLFSLockByRepoID(repository.ID, 0, 0)
251+
cursor := ctx.QueryInt("cursor")
252+
if cursor < 0 {
253+
cursor = 0
254+
}
255+
limit := ctx.QueryInt("limit")
256+
if limit > setting.LFS.LocksPagingNum && setting.LFS.LocksPagingNum > 0 {
257+
limit = setting.LFS.LocksPagingNum
258+
} else if limit < 0 {
259+
limit = 0
260+
}
261+
lockList, err := models.GetLFSLockByRepoID(repository.ID, cursor, limit)
224262
if err != nil {
263+
log.Error("Unable to list locks for repository ID[%d]: Error: %v", repository.ID, err)
225264
ctx.JSON(500, api.LFSLockError{
226-
Message: "unable to list locks : " + err.Error(),
265+
Message: "unable to list locks : Internal Server Error",
227266
})
228267
return
229268
}
269+
next := ""
270+
if limit > 0 && len(lockList) == limit {
271+
next = strconv.Itoa(cursor + 1)
272+
}
230273
lockOursListAPI := make([]*api.LFSLock, 0, len(lockList))
231274
lockTheirsListAPI := make([]*api.LFSLock, 0, len(lockList))
232275
for _, l := range lockList {
@@ -239,12 +282,14 @@ func VerifyLockHandler(ctx *context.Context) {
239282
ctx.JSON(200, api.LFSLockListVerify{
240283
Ours: lockOursListAPI,
241284
Theirs: lockTheirsListAPI,
285+
Next: next,
242286
})
243287
}
244288

245289
// UnLockHandler delete locks
246290
func UnLockHandler(ctx *context.Context) {
247291
if !checkIsValidRequest(ctx) {
292+
// Status is written in checkIsValidRequest
248293
return
249294
}
250295
ctx.Resp.Header().Set("Content-Type", metaMediaType)
@@ -255,7 +300,7 @@ func UnLockHandler(ctx *context.Context) {
255300

256301
repository, err := models.GetRepositoryByOwnerAndName(userName, repoName)
257302
if err != nil {
258-
log.Debug("Could not find repository: %s/%s - %s", userName, repoName, err)
303+
log.Error("Unable to get repository: %s/%s Error: %v", userName, repoName, err)
259304
writeStatus(ctx, 404)
260305
return
261306
}
@@ -275,6 +320,7 @@ func UnLockHandler(ctx *context.Context) {
275320
defer bodyReader.Close()
276321
dec := json.NewDecoder(bodyReader)
277322
if err := dec.Decode(&req); err != nil {
323+
log.Warn("Failed to decode lock request as json. Error: %v", err)
278324
writeStatus(ctx, 400)
279325
return
280326
}
@@ -288,8 +334,9 @@ func UnLockHandler(ctx *context.Context) {
288334
})
289335
return
290336
}
337+
log.Error("Unable to DeleteLFSLockByID[%d] by user %-v with force %t: Error: %v", ctx.ParamsInt64("lid"), ctx.User, req.Force, err)
291338
ctx.JSON(500, api.LFSLockError{
292-
Message: "unable to delete lock : " + err.Error(),
339+
Message: "unable to delete lock : Internal Server Error",
293340
})
294341
return
295342
}

0 commit comments

Comments
 (0)