Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
OpenDAS
ollama
Commits
2c6189f4
Unverified
Commit
2c6189f4
authored
Nov 01, 2023
by
Michael Yang
Committed by
GitHub
Nov 01, 2023
Browse files
Merge pull request #750 from jmorganca/mxyng/concurrent-uploads
concurrent uploads
parents
c05ab9a8
115fc56e
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
287 additions
and
189 deletions
+287
-189
server/download.go
server/download.go
+3
-2
server/images.go
server/images.go
+2
-60
server/routes.go
server/routes.go
+3
-1
server/upload.go
server/upload.go
+279
-126
No files found.
server/download.go
View file @
2c6189f4
...
@@ -134,7 +134,6 @@ func (b *blobDownload) Run(ctx context.Context, requestURL *url.URL, opts *Regis
...
@@ -134,7 +134,6 @@ func (b *blobDownload) Run(ctx context.Context, requestURL *url.URL, opts *Regis
func
(
b
*
blobDownload
)
run
(
ctx
context
.
Context
,
requestURL
*
url
.
URL
,
opts
*
RegistryOptions
)
error
{
func
(
b
*
blobDownload
)
run
(
ctx
context
.
Context
,
requestURL
*
url
.
URL
,
opts
*
RegistryOptions
)
error
{
defer
blobDownloadManager
.
Delete
(
b
.
Digest
)
defer
blobDownloadManager
.
Delete
(
b
.
Digest
)
ctx
,
b
.
CancelFunc
=
context
.
WithCancel
(
ctx
)
ctx
,
b
.
CancelFunc
=
context
.
WithCancel
(
ctx
)
file
,
err
:=
os
.
OpenFile
(
b
.
Name
+
"-partial"
,
os
.
O_CREATE
|
os
.
O_RDWR
,
0644
)
file
,
err
:=
os
.
OpenFile
(
b
.
Name
+
"-partial"
,
os
.
O_CREATE
|
os
.
O_RDWR
,
0644
)
...
@@ -170,7 +169,7 @@ func (b *blobDownload) run(ctx context.Context, requestURL *url.URL, opts *Regis
...
@@ -170,7 +169,7 @@ func (b *blobDownload) run(ctx context.Context, requestURL *url.URL, opts *Regis
}
}
}
}
return
err
ors
.
New
(
"max r
etries
e
xceeded
"
)
return
err
MaxR
etries
E
xceeded
})
})
}
}
...
@@ -308,6 +307,8 @@ type downloadOpts struct {
...
@@ -308,6 +307,8 @@ type downloadOpts struct {
const
maxRetries
=
3
const
maxRetries
=
3
var
errMaxRetriesExceeded
=
errors
.
New
(
"max retries exceeded"
)
// downloadBlob downloads a blob from the registry and stores it in the blobs directory
// downloadBlob downloads a blob from the registry and stores it in the blobs directory
func
downloadBlob
(
ctx
context
.
Context
,
opts
downloadOpts
)
error
{
func
downloadBlob
(
ctx
context
.
Context
,
opts
downloadOpts
)
error
{
fp
,
err
:=
GetBlobsPath
(
opts
.
digest
)
fp
,
err
:=
GetBlobsPath
(
opts
.
digest
)
...
...
server/images.go
View file @
2c6189f4
...
@@ -981,46 +981,7 @@ func PushModel(ctx context.Context, name string, regOpts *RegistryOptions, fn fu
...
@@ -981,46 +981,7 @@ func PushModel(ctx context.Context, name string, regOpts *RegistryOptions, fn fu
layers
=
append
(
layers
,
&
manifest
.
Config
)
layers
=
append
(
layers
,
&
manifest
.
Config
)
for
_
,
layer
:=
range
layers
{
for
_
,
layer
:=
range
layers
{
exists
,
err
:=
checkBlobExistence
(
ctx
,
mp
,
layer
.
Digest
,
regOpts
)
if
err
:=
uploadBlob
(
ctx
,
mp
,
layer
,
regOpts
,
fn
);
err
!=
nil
{
if
err
!=
nil
{
return
err
}
if
exists
{
fn
(
api
.
ProgressResponse
{
Status
:
"using existing layer"
,
Digest
:
layer
.
Digest
,
Total
:
layer
.
Size
,
Completed
:
layer
.
Size
,
})
log
.
Printf
(
"Layer %s already exists"
,
layer
.
Digest
)
continue
}
fn
(
api
.
ProgressResponse
{
Status
:
"starting upload"
,
Digest
:
layer
.
Digest
,
Total
:
layer
.
Size
,
})
location
,
chunkSize
,
err
:=
startUpload
(
ctx
,
mp
,
layer
,
regOpts
)
if
err
!=
nil
{
log
.
Printf
(
"couldn't start upload: %v"
,
err
)
return
err
}
if
strings
.
HasPrefix
(
filepath
.
Base
(
location
.
Path
),
"sha256:"
)
{
layer
.
Digest
=
filepath
.
Base
(
location
.
Path
)
fn
(
api
.
ProgressResponse
{
Status
:
"using existing layer"
,
Digest
:
layer
.
Digest
,
Total
:
layer
.
Size
,
Completed
:
layer
.
Size
,
})
continue
}
if
err
:=
uploadBlob
(
ctx
,
location
,
layer
,
chunkSize
,
regOpts
,
fn
);
err
!=
nil
{
log
.
Printf
(
"error uploading blob: %v"
,
err
)
log
.
Printf
(
"error uploading blob: %v"
,
err
)
return
err
return
err
}
}
...
@@ -1218,24 +1179,7 @@ func GetSHA256Digest(r io.Reader) (string, int64) {
...
@@ -1218,24 +1179,7 @@ func GetSHA256Digest(r io.Reader) (string, int64) {
return
fmt
.
Sprintf
(
"sha256:%x"
,
h
.
Sum
(
nil
)),
n
return
fmt
.
Sprintf
(
"sha256:%x"
,
h
.
Sum
(
nil
)),
n
}
}
// Function to check if a blob already exists in the Docker registry
func
checkBlobExistence
(
ctx
context
.
Context
,
mp
ModelPath
,
digest
string
,
regOpts
*
RegistryOptions
)
(
bool
,
error
)
{
requestURL
:=
mp
.
BaseURL
()
requestURL
=
requestURL
.
JoinPath
(
"v2"
,
mp
.
GetNamespaceRepository
(),
"blobs"
,
digest
)
resp
,
err
:=
makeRequest
(
ctx
,
"HEAD"
,
requestURL
,
nil
,
nil
,
regOpts
)
if
err
!=
nil
{
log
.
Printf
(
"couldn't check for blob: %v"
,
err
)
return
false
,
err
}
defer
resp
.
Body
.
Close
()
// Check for success: If the blob exists, the Docker registry will respond with a 200 OK
return
resp
.
StatusCode
<
http
.
StatusBadRequest
,
nil
}
func
makeRequestWithRetry
(
ctx
context
.
Context
,
method
string
,
requestURL
*
url
.
URL
,
headers
http
.
Header
,
body
io
.
ReadSeeker
,
regOpts
*
RegistryOptions
)
(
*
http
.
Response
,
error
)
{
func
makeRequestWithRetry
(
ctx
context
.
Context
,
method
string
,
requestURL
*
url
.
URL
,
headers
http
.
Header
,
body
io
.
ReadSeeker
,
regOpts
*
RegistryOptions
)
(
*
http
.
Response
,
error
)
{
var
status
string
for
try
:=
0
;
try
<
maxRetries
;
try
++
{
for
try
:=
0
;
try
<
maxRetries
;
try
++
{
resp
,
err
:=
makeRequest
(
ctx
,
method
,
requestURL
,
headers
,
body
,
regOpts
)
resp
,
err
:=
makeRequest
(
ctx
,
method
,
requestURL
,
headers
,
body
,
regOpts
)
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -1243,8 +1187,6 @@ func makeRequestWithRetry(ctx context.Context, method string, requestURL *url.UR
...
@@ -1243,8 +1187,6 @@ func makeRequestWithRetry(ctx context.Context, method string, requestURL *url.UR
return
nil
,
err
return
nil
,
err
}
}
status
=
resp
.
Status
switch
{
switch
{
case
resp
.
StatusCode
==
http
.
StatusUnauthorized
:
case
resp
.
StatusCode
==
http
.
StatusUnauthorized
:
auth
:=
resp
.
Header
.
Get
(
"www-authenticate"
)
auth
:=
resp
.
Header
.
Get
(
"www-authenticate"
)
...
@@ -1270,7 +1212,7 @@ func makeRequestWithRetry(ctx context.Context, method string, requestURL *url.UR
...
@@ -1270,7 +1212,7 @@ func makeRequestWithRetry(ctx context.Context, method string, requestURL *url.UR
}
}
}
}
return
nil
,
fmt
.
Errorf
(
"max retry exceeded: %v"
,
status
)
return
nil
,
errMaxRetriesExceeded
}
}
func
makeRequest
(
ctx
context
.
Context
,
method
string
,
requestURL
*
url
.
URL
,
headers
http
.
Header
,
body
io
.
Reader
,
regOpts
*
RegistryOptions
)
(
*
http
.
Response
,
error
)
{
func
makeRequest
(
ctx
context
.
Context
,
method
string
,
requestURL
*
url
.
URL
,
headers
http
.
Header
,
body
io
.
Reader
,
regOpts
*
RegistryOptions
)
(
*
http
.
Response
,
error
)
{
...
...
server/routes.go
View file @
2c6189f4
...
@@ -365,7 +365,9 @@ func PushModelHandler(c *gin.Context) {
...
@@ -365,7 +365,9 @@ func PushModelHandler(c *gin.Context) {
Insecure
:
req
.
Insecure
,
Insecure
:
req
.
Insecure
,
}
}
ctx
:=
context
.
Background
()
ctx
,
cancel
:=
context
.
WithCancel
(
c
.
Request
.
Context
())
defer
cancel
()
if
err
:=
PushModel
(
ctx
,
req
.
Name
,
regOpts
,
fn
);
err
!=
nil
{
if
err
:=
PushModel
(
ctx
,
req
.
Name
,
regOpts
,
fn
);
err
!=
nil
{
ch
<-
gin
.
H
{
"error"
:
err
.
Error
()}
ch
<-
gin
.
H
{
"error"
:
err
.
Error
()}
}
}
...
...
server/upload.go
View file @
2c6189f4
...
@@ -2,218 +2,371 @@ package server
...
@@ -2,218 +2,371 @@ package server
import
(
import
(
"context"
"context"
"crypto/md5"
"errors"
"errors"
"fmt"
"fmt"
"hash"
"io"
"io"
"log"
"log"
"net/http"
"net/http"
"net/url"
"net/url"
"os"
"os"
"str
conv
"
"str
ings
"
"sync"
"sync"
"sync/atomic"
"time"
"github.com/jmorganca/ollama/api"
"github.com/jmorganca/ollama/api"
"github.com/jmorganca/ollama/format"
"golang.org/x/sync/errgroup"
)
)
var
blobUploadManager
sync
.
Map
type
blobUpload
struct
{
*
Layer
Total
int64
Completed
atomic
.
Int64
Parts
[]
blobUploadPart
nextURL
chan
*
url
.
URL
context
.
CancelFunc
done
bool
err
error
references
atomic
.
Int32
}
type
blobUploadPart
struct
{
// N is the part number
N
int
Offset
int64
Size
int64
hash
.
Hash
}
const
(
const
(
redirectChunkSize
int64
=
1024
*
1024
*
1024
numUploadParts
=
64
regularChunkSize
int64
=
95
*
1024
*
1024
minUploadPartSize
int64
=
95
*
1000
*
1000
maxUploadPartSize
int64
=
1000
*
1000
*
1000
)
)
func
startUpload
(
ctx
context
.
Context
,
mp
ModelPath
,
layer
*
Layer
,
regOpts
*
RegistryOptions
)
(
*
url
.
URL
,
int64
,
error
)
{
func
(
b
*
blobUpload
)
Prepare
(
ctx
context
.
Context
,
requestURL
*
url
.
URL
,
opts
*
RegistryOptions
)
error
{
requestURL
:=
mp
.
BaseURL
()
p
,
err
:=
GetBlobsPath
(
b
.
Digest
)
requestURL
=
requestURL
.
JoinPath
(
"v2"
,
mp
.
GetNamespaceRepository
(),
"blobs/uploads/"
)
if
err
!=
nil
{
if
layer
.
From
!=
""
{
return
err
}
if
b
.
From
!=
""
{
values
:=
requestURL
.
Query
()
values
:=
requestURL
.
Query
()
values
.
Add
(
"mount"
,
layer
.
Digest
)
values
.
Add
(
"mount"
,
b
.
Digest
)
values
.
Add
(
"from"
,
layer
.
From
)
values
.
Add
(
"from"
,
b
.
From
)
requestURL
.
RawQuery
=
values
.
Encode
()
requestURL
.
RawQuery
=
values
.
Encode
()
}
}
resp
,
err
:=
makeRequestWithRetry
(
ctx
,
"POST"
,
requestURL
,
nil
,
nil
,
regO
pts
)
resp
,
err
:=
makeRequestWithRetry
(
ctx
,
"POST"
,
requestURL
,
nil
,
nil
,
o
pts
)
if
err
!=
nil
{
if
err
!=
nil
{
log
.
Printf
(
"couldn't start upload: %v"
,
err
)
return
err
return
nil
,
0
,
err
}
}
defer
resp
.
Body
.
Close
()
defer
resp
.
Body
.
Close
()
location
:=
resp
.
Header
.
Get
(
"Docker-Upload-Location"
)
location
:=
resp
.
Header
.
Get
(
"Docker-Upload-Location"
)
chunkSize
:=
redirectChunkSize
if
location
==
""
{
if
location
==
""
{
location
=
resp
.
Header
.
Get
(
"Location"
)
location
=
resp
.
Header
.
Get
(
"Location"
)
chunkSize
=
regularChunkSize
}
}
locationURL
,
err
:=
url
.
Parse
(
location
)
fi
,
err
:=
os
.
Stat
(
p
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
0
,
err
return
err
}
}
return
locationURL
,
chunkSize
,
nil
b
.
Total
=
fi
.
Size
()
}
func
uploadBlob
(
ctx
context
.
Context
,
requestURL
*
url
.
URL
,
layer
*
Layer
,
chunkSize
int64
,
regOpts
*
RegistryOptions
,
fn
func
(
api
.
ProgressResponse
))
error
{
var
size
=
b
.
Total
/
numUploadParts
// TODO allow resumability
switch
{
// TODO allow canceling uploads via DELETE
case
size
<
minUploadPartSize
:
size
=
minUploadPartSize
case
size
>
maxUploadPartSize
:
size
=
maxUploadPartSize
}
fp
,
err
:=
GetBlobsPath
(
layer
.
Digest
)
var
offset
int64
if
err
!=
nil
{
for
offset
<
fi
.
Size
()
{
return
err
if
offset
+
size
>
fi
.
Size
()
{
size
=
fi
.
Size
()
-
offset
}
// set part.N to the current number of parts
b
.
Parts
=
append
(
b
.
Parts
,
blobUploadPart
{
N
:
len
(
b
.
Parts
),
Offset
:
offset
,
Size
:
size
,
Hash
:
md5
.
New
()})
offset
+=
size
}
}
f
,
err
:=
os
.
Open
(
fp
)
log
.
Printf
(
"uploading %s in %d %s part(s)"
,
b
.
Digest
[
7
:
19
],
len
(
b
.
Parts
),
format
.
HumanBytes
(
size
))
requestURL
,
err
=
url
.
Parse
(
location
)
if
err
!=
nil
{
if
err
!=
nil
{
return
err
return
err
}
}
defer
f
.
Close
()
pw
:=
ProgressWriter
{
b
.
nextURL
=
make
(
chan
*
url
.
URL
,
1
)
status
:
fmt
.
Sprintf
(
"uploading %s"
,
layer
.
Digest
),
b
.
nextURL
<-
requestURL
digest
:
layer
.
Digest
,
return
nil
total
:
layer
.
Size
,
}
fn
:
fn
,
// Run uploads blob parts to the upstream. If the upstream supports redirection, parts will be uploaded
// in parallel as defined by Prepare. Otherwise, parts will be uploaded serially. Run sets b.err on error.
func
(
b
*
blobUpload
)
Run
(
ctx
context
.
Context
,
opts
*
RegistryOptions
)
{
defer
blobUploadManager
.
Delete
(
b
.
Digest
)
ctx
,
b
.
CancelFunc
=
context
.
WithCancel
(
ctx
)
p
,
err
:=
GetBlobsPath
(
b
.
Digest
)
if
err
!=
nil
{
b
.
err
=
err
return
}
}
for
offset
:=
int64
(
0
);
offset
<
layer
.
Size
;
{
f
,
err
:=
os
.
Open
(
p
)
chunk
:=
layer
.
Size
-
offset
if
err
!=
nil
{
if
chunk
>
chunkSize
{
b
.
err
=
err
chunk
=
chunkSize
return
}
}
defer
f
.
Close
()
resp
,
err
:=
uploadBlobChunk
(
ctx
,
http
.
MethodPatch
,
requestURL
,
f
,
offset
,
chunk
,
regOpts
,
&
pw
)
g
,
inner
:=
errgroup
.
WithContext
(
ctx
)
if
err
!=
nil
{
g
.
SetLimit
(
numUploadParts
)
fn
(
api
.
ProgressResponse
{
for
i
:=
range
b
.
Parts
{
Status
:
fmt
.
Sprintf
(
"error uploading chunk: %v"
,
err
),
part
:=
&
b
.
Parts
[
i
]
Digest
:
layer
.
Digest
,
select
{
Total
:
layer
.
Size
,
case
<-
inner
.
Done
()
:
Completed
:
offset
,
case
requestURL
:=
<-
b
.
nextURL
:
g
.
Go
(
func
()
error
{
for
try
:=
0
;
try
<
maxRetries
;
try
++
{
r
:=
io
.
NewSectionReader
(
f
,
part
.
Offset
,
part
.
Size
)
err
:=
b
.
uploadChunk
(
inner
,
http
.
MethodPatch
,
requestURL
,
r
,
part
,
opts
)
switch
{
case
errors
.
Is
(
err
,
context
.
Canceled
)
:
return
err
case
errors
.
Is
(
err
,
errMaxRetriesExceeded
)
:
return
err
case
err
!=
nil
:
log
.
Printf
(
"%s part %d attempt %d failed: %v, retrying"
,
b
.
Digest
[
7
:
19
],
part
.
N
,
try
,
err
)
continue
}
return
nil
}
return
errMaxRetriesExceeded
})
})
return
err
}
}
}
offset
+=
chunk
if
err
:=
g
.
Wait
();
err
!=
nil
{
location
:=
resp
.
Header
.
Get
(
"Docker-Upload-Location"
)
b
.
err
=
err
if
location
==
""
{
return
location
=
resp
.
Header
.
Get
(
"Location"
)
}
}
requestURL
,
err
=
url
.
Parse
(
location
)
requestURL
:=
<-
b
.
nextURL
if
err
!=
nil
{
return
err
var
sb
strings
.
Builder
}
for
_
,
part
:=
range
b
.
Parts
{
sb
.
Write
(
part
.
Sum
(
nil
))
}
}
md5sum
:=
md5
.
Sum
([]
byte
(
sb
.
String
()))
values
:=
requestURL
.
Query
()
values
:=
requestURL
.
Query
()
values
.
Add
(
"digest"
,
layer
.
Digest
)
values
.
Add
(
"digest"
,
b
.
Digest
)
values
.
Add
(
"etag"
,
fmt
.
Sprintf
(
"%x-%d"
,
md5sum
,
len
(
b
.
Parts
)))
requestURL
.
RawQuery
=
values
.
Encode
()
requestURL
.
RawQuery
=
values
.
Encode
()
headers
:=
make
(
http
.
Header
)
headers
:=
make
(
http
.
Header
)
headers
.
Set
(
"Content-Type"
,
"application/octet-stream"
)
headers
.
Set
(
"Content-Type"
,
"application/octet-stream"
)
headers
.
Set
(
"Content-Length"
,
"0"
)
headers
.
Set
(
"Content-Length"
,
"0"
)
// finish the upload
resp
,
err
:=
makeRequestWithRetry
(
ctx
,
"PUT"
,
requestURL
,
headers
,
nil
,
opts
)
resp
,
err
:=
makeRequest
(
ctx
,
"PUT"
,
requestURL
,
headers
,
nil
,
regOpts
)
if
err
!=
nil
{
if
err
!=
nil
{
log
.
Printf
(
"couldn't finish upload: %v"
,
err
)
b
.
err
=
err
return
err
return
}
}
defer
resp
.
Body
.
Close
()
defer
resp
.
Body
.
Close
()
if
resp
.
StatusCode
>=
http
.
StatusBadRequest
{
b
.
done
=
true
body
,
_
:=
io
.
ReadAll
(
resp
.
Body
)
return
fmt
.
Errorf
(
"on finish upload registry responded with code %d: %v"
,
resp
.
StatusCode
,
string
(
body
))
}
return
nil
}
}
func
uploadBlobChunk
(
ctx
context
.
Context
,
method
string
,
requestURL
*
url
.
URL
,
r
io
.
ReaderAt
,
offset
,
limit
int64
,
opts
*
RegistryOptions
,
pw
*
ProgressWriter
)
(
*
http
.
Response
,
error
)
{
func
(
b
*
blobUpload
)
uploadChunk
(
ctx
context
.
Context
,
method
string
,
requestURL
*
url
.
URL
,
rs
io
.
ReadSeeker
,
part
*
blobUploadPart
,
opts
*
RegistryOptions
)
error
{
sectionReader
:=
io
.
NewSectionReader
(
r
,
offset
,
limit
)
headers
:=
make
(
http
.
Header
)
headers
:=
make
(
http
.
Header
)
headers
.
Set
(
"Content-Type"
,
"application/octet-stream"
)
headers
.
Set
(
"Content-Type"
,
"application/octet-stream"
)
headers
.
Set
(
"Content-Length"
,
strconv
.
Itoa
(
int
(
limit
)
))
headers
.
Set
(
"Content-Length"
,
fmt
.
Sprintf
(
"%d"
,
part
.
Size
))
headers
.
Set
(
"X-Redirect-Uploads"
,
"1"
)
headers
.
Set
(
"X-Redirect-Uploads"
,
"1"
)
if
method
==
http
.
MethodPatch
{
if
method
==
http
.
MethodPatch
{
headers
.
Set
(
"Content-Range"
,
fmt
.
Sprintf
(
"%d-%d"
,
o
ffset
,
offset
+
sectionReader
.
Size
()
-
1
))
headers
.
Set
(
"Content-Range"
,
fmt
.
Sprintf
(
"%d-%d"
,
part
.
O
ffset
,
part
.
Offset
+
part
.
Size
-
1
))
}
}
for
try
:=
0
;
try
<
maxRetries
;
try
++
{
buw
:=
blobUploadWriter
{
blobUpload
:
b
}
resp
,
err
:=
makeRequest
(
ctx
,
method
,
requestURL
,
headers
,
io
.
TeeReader
(
sectionReader
,
pw
),
opts
)
resp
,
err
:=
makeRequest
(
ctx
,
method
,
requestURL
,
headers
,
io
.
TeeReader
(
rs
,
io
.
MultiWriter
(
&
buw
,
part
.
Hash
)
),
opts
)
if
err
!=
nil
&&
!
errors
.
Is
(
err
,
io
.
EOF
)
{
if
err
!=
nil
{
return
nil
,
err
return
err
}
}
defer
resp
.
Body
.
Close
()
defer
resp
.
Body
.
Close
()
switch
{
location
:=
resp
.
Header
.
Get
(
"Docker-Upload-Location"
)
case
resp
.
StatusCode
==
http
.
StatusTemporaryRedirect
:
if
location
==
""
{
location
,
err
:=
resp
.
Location
()
location
=
resp
.
Header
.
Get
(
"Location"
)
if
err
!=
nil
{
}
return
nil
,
err
}
nextURL
,
err
:=
url
.
Parse
(
location
)
if
err
!=
nil
{
return
err
}
switch
{
case
resp
.
StatusCode
==
http
.
StatusTemporaryRedirect
:
b
.
nextURL
<-
nextURL
redirectURL
,
err
:=
resp
.
Location
()
if
err
!=
nil
{
return
err
}
pw
.
completed
=
offset
for
try
:=
0
;
try
<
maxRetries
;
try
++
{
if
_
,
err
:=
uploadBlobChunk
(
ctx
,
http
.
MethodPut
,
location
,
r
,
offset
,
limit
,
nil
,
pw
);
err
!=
nil
{
rs
.
Seek
(
0
,
io
.
SeekStart
)
// retry
b
.
Completed
.
Add
(
-
buw
.
written
)
log
.
Printf
(
"retrying redirected upload: %v"
,
err
)
buw
.
written
=
0
part
.
Hash
=
md5
.
New
()
err
:=
b
.
uploadChunk
(
ctx
,
http
.
MethodPut
,
redirectURL
,
rs
,
part
,
nil
)
switch
{
case
errors
.
Is
(
err
,
context
.
Canceled
)
:
return
err
case
errors
.
Is
(
err
,
errMaxRetriesExceeded
)
:
return
err
case
err
!=
nil
:
log
.
Printf
(
"%s part %d attempt %d failed: %v, retrying"
,
b
.
Digest
[
7
:
19
],
part
.
N
,
try
,
err
)
continue
continue
}
}
return
resp
,
nil
return
nil
case
resp
.
StatusCode
==
http
.
StatusUnauthorized
:
}
auth
:=
resp
.
Header
.
Get
(
"www-authenticate"
)
authRedir
:=
ParseAuthRedirectString
(
auth
)
token
,
err
:=
getAuthToken
(
ctx
,
authRedir
)
if
err
!=
nil
{
return
nil
,
err
}
opts
.
Token
=
token
return
errMaxRetriesExceeded
pw
.
completed
=
offset
case
resp
.
StatusCode
==
http
.
StatusUnauthorized
:
sectionReader
=
io
.
NewSectionReader
(
r
,
offset
,
limit
)
auth
:=
resp
.
Header
.
Get
(
"www-authenticate"
)
continue
authRedir
:=
ParseAuthRedirectString
(
auth
)
case
resp
.
StatusCode
>=
http
.
StatusBadRequest
:
token
,
err
:=
getAuthToken
(
ctx
,
authRedir
)
body
,
_
:=
io
.
ReadAll
(
resp
.
Body
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"on upload registry responded with code %d: %s"
,
resp
.
StatusCode
,
body
)
return
err
}
}
return
resp
,
nil
opts
.
Token
=
token
fallthrough
case
resp
.
StatusCode
>=
http
.
StatusBadRequest
:
body
,
err
:=
io
.
ReadAll
(
resp
.
Body
)
if
err
!=
nil
{
return
err
}
rs
.
Seek
(
0
,
io
.
SeekStart
)
b
.
Completed
.
Add
(
-
buw
.
written
)
buw
.
written
=
0
return
fmt
.
Errorf
(
"http status %d %s: %s"
,
resp
.
StatusCode
,
resp
.
Status
,
body
)
}
if
method
==
http
.
MethodPatch
{
b
.
nextURL
<-
nextURL
}
}
return
nil
,
fmt
.
Errorf
(
"max retries exceeded"
)
return
nil
}
func
(
b
*
blobUpload
)
acquire
()
{
b
.
references
.
Add
(
1
)
}
}
type
ProgressWriter
struct
{
func
(
b
*
blobUpload
)
release
()
{
status
string
if
b
.
references
.
Add
(
-
1
)
==
0
{
digest
string
b
.
CancelFunc
()
bucket
int64
}
completed
int64
total
int64
fn
func
(
api
.
ProgressResponse
)
mu
sync
.
Mutex
}
}
func
(
pw
*
ProgressWriter
)
Write
(
b
[]
byte
)
(
int
,
error
)
{
func
(
b
*
blobUpload
)
Wait
(
ctx
context
.
Context
,
fn
func
(
api
.
ProgressResponse
))
error
{
pw
.
mu
.
Lock
()
b
.
acquire
()
defer
pw
.
mu
.
Unlock
()
defer
b
.
release
()
n
:=
len
(
b
)
ticker
:=
time
.
NewTicker
(
60
*
time
.
Millisecond
)
pw
.
bucket
+=
int64
(
n
)
for
{
select
{
// throttle status updates to not spam the client
case
<-
ticker
.
C
:
if
pw
.
bucket
>=
1024
*
1024
||
pw
.
completed
+
pw
.
bucket
>=
pw
.
total
{
case
<-
ctx
.
Done
()
:
pw
.
completed
+=
pw
.
bucket
return
ctx
.
Err
()
pw
.
fn
(
api
.
ProgressResponse
{
}
Status
:
pw
.
status
,
Digest
:
pw
.
digest
,
fn
(
api
.
ProgressResponse
{
Total
:
pw
.
total
,
Status
:
fmt
.
Sprintf
(
"uploading %s"
,
b
.
Digest
),
Completed
:
pw
.
completed
,
Digest
:
b
.
Digest
,
Total
:
b
.
Total
,
Completed
:
b
.
Completed
.
Load
(),
})
})
pw
.
bucket
=
0
if
b
.
done
||
b
.
err
!=
nil
{
return
b
.
err
}
}
}
}
type
blobUploadWriter
struct
{
written
int64
*
blobUpload
}
func
(
b
*
blobUploadWriter
)
Write
(
p
[]
byte
)
(
n
int
,
err
error
)
{
n
=
len
(
p
)
b
.
written
+=
int64
(
n
)
b
.
Completed
.
Add
(
int64
(
n
))
return
n
,
nil
return
n
,
nil
}
}
func
uploadBlob
(
ctx
context
.
Context
,
mp
ModelPath
,
layer
*
Layer
,
opts
*
RegistryOptions
,
fn
func
(
api
.
ProgressResponse
))
error
{
requestURL
:=
mp
.
BaseURL
()
requestURL
=
requestURL
.
JoinPath
(
"v2"
,
mp
.
GetNamespaceRepository
(),
"blobs"
,
layer
.
Digest
)
resp
,
err
:=
makeRequest
(
ctx
,
"HEAD"
,
requestURL
,
nil
,
nil
,
opts
)
if
err
!=
nil
{
return
err
}
defer
resp
.
Body
.
Close
()
switch
resp
.
StatusCode
{
case
http
.
StatusNotFound
:
case
http
.
StatusOK
:
fn
(
api
.
ProgressResponse
{
Status
:
fmt
.
Sprintf
(
"uploading %s"
,
layer
.
Digest
),
Digest
:
layer
.
Digest
,
Total
:
layer
.
Size
,
Completed
:
layer
.
Size
,
})
return
nil
default
:
return
fmt
.
Errorf
(
"unexpected status code %d"
,
resp
.
StatusCode
)
}
data
,
ok
:=
blobUploadManager
.
LoadOrStore
(
layer
.
Digest
,
&
blobUpload
{
Layer
:
layer
})
upload
:=
data
.
(
*
blobUpload
)
if
!
ok
{
requestURL
:=
mp
.
BaseURL
()
requestURL
=
requestURL
.
JoinPath
(
"v2"
,
mp
.
GetNamespaceRepository
(),
"blobs/uploads/"
)
if
err
:=
upload
.
Prepare
(
ctx
,
requestURL
,
opts
);
err
!=
nil
{
blobUploadManager
.
Delete
(
layer
.
Digest
)
return
err
}
go
upload
.
Run
(
context
.
Background
(),
opts
)
}
return
upload
.
Wait
(
ctx
,
fn
)
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment