Skip to content

Commit 825e7f2

Browse files
committed
api: select xet object upload mode
1 parent 78a2c4a commit 825e7f2

5 files changed

Lines changed: 100 additions & 3 deletions

File tree

api/swagger.yml

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,19 @@ components:
415415
type: boolean
416416
default: false
417417

418+
ObjectUploadMode:
419+
type: object
420+
required:
421+
- upload_mode
422+
properties:
423+
upload_mode:
424+
type: string
425+
enum: [regular, xet]
426+
description: |
427+
Server-selected upload mode for a regular object upload.
428+
"regular" means the client should use POST /objects.
429+
"xet" means the client may upload through XET and link an xet:// physical address.
430+
418431
ObjectUserMetadata:
419432
type: object
420433
additionalProperties:
@@ -4900,13 +4913,24 @@ paths:
49004913
required: true
49014914
schema:
49024915
type: string
4916+
- in: query
4917+
name: size_bytes
4918+
description: Size of the object content the client plans to upload.
4919+
required: false
4920+
schema:
4921+
type: integer
4922+
format: int64
49034923
get:
49044924
tags:
49054925
- internal
49064926
operationId: uploadObjectPreflight
49074927
responses:
4908-
204:
4909-
description: User has permissions to upload this object. This does not guarantee that the upload will be successful or even possible. It indicates only the permission at the time of calling this endpoint
4928+
200:
4929+
description: User has permissions to upload this object and the server-selected upload mode.
4930+
content:
4931+
application/json:
4932+
schema:
4933+
$ref: "#/components/schemas/ObjectUploadMode"
49104934
401:
49114935
$ref: "#/components/responses/Unauthorized"
49124936
403:

pkg/api/controller.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3298,7 +3298,23 @@ func (c *Controller) UploadObjectPreflight(w http.ResponseWriter, r *http.Reques
32983298
ctx := r.Context()
32993299
c.LogAction(ctx, "put_object_preflight", r, repository, branch, "")
33003300

3301-
writeResponse(w, r, http.StatusNoContent, nil)
3301+
writeResponse(w, r, http.StatusOK, apigen.ObjectUploadMode{
3302+
UploadMode: c.objectUploadMode(params.SizeBytes),
3303+
})
3304+
}
3305+
3306+
func (c *Controller) objectUploadMode(sizeBytes *int64) string {
3307+
if sizeBytes == nil || *sizeBytes <= 0 {
3308+
return "regular"
3309+
}
3310+
minSize := c.Config.GetBaseConfig().XET.Upload.MinSizeBytes
3311+
if minSize <= 0 {
3312+
return "regular"
3313+
}
3314+
if *sizeBytes >= minSize {
3315+
return "xet"
3316+
}
3317+
return "regular"
33023318
}
33033319

33043320
func (c *Controller) UploadObject(w http.ResponseWriter, r *http.Request, owner, repository, branch string, params apigen.UploadObjectParams) {

pkg/api/controller_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,59 @@ func TestController_LinkXETPhysicalAddressWritesFileRef(t *testing.T) {
126126
require.Equal(t, []byte{}, fileRef.Value)
127127
}
128128

129+
func TestController_UploadObjectPreflightReturnsRegularForSmallObject(t *testing.T) {
130+
clt, deps := setupClientWithAdmin(t)
131+
ctx := context.Background()
132+
repo := testUniqueRepoName()
133+
const branch = "main"
134+
_, err := deps.catalog.CreateRepository(ctx, repo, "", onBlock(deps, "bucket/prefix"), branch, false)
135+
require.NoError(t, err)
136+
137+
resp, err := clt.UploadObjectPreflightWithResponse(ctx, apigen.RepositoryOwner(repo), apigen.RepositoryName(repo), branch, &apigen.UploadObjectPreflightParams{
138+
Path: "small.txt",
139+
SizeBytes: apiutil.Ptr(int64(1024)),
140+
})
141+
require.NoError(t, err)
142+
require.Equal(t, http.StatusOK, resp.StatusCode())
143+
require.NotNil(t, resp.JSON200)
144+
require.Equal(t, "regular", resp.JSON200.UploadMode)
145+
}
146+
147+
func TestController_UploadObjectPreflightReturnsXETForLargeObject(t *testing.T) {
148+
clt, deps := setupClientWithAdmin(t)
149+
ctx := context.Background()
150+
repo := testUniqueRepoName()
151+
const branch = "main"
152+
_, err := deps.catalog.CreateRepository(ctx, repo, "", onBlock(deps, "bucket/prefix"), branch, false)
153+
require.NoError(t, err)
154+
155+
resp, err := clt.UploadObjectPreflightWithResponse(ctx, apigen.RepositoryOwner(repo), apigen.RepositoryName(repo), branch, &apigen.UploadObjectPreflightParams{
156+
Path: "models/model.bin",
157+
SizeBytes: apiutil.Ptr(int64(8 * 1024 * 1024)),
158+
})
159+
require.NoError(t, err)
160+
require.Equal(t, http.StatusOK, resp.StatusCode())
161+
require.NotNil(t, resp.JSON200)
162+
require.Equal(t, "xet", resp.JSON200.UploadMode)
163+
}
164+
165+
func TestController_UploadObjectPreflightReturnsRegularWhenSizeUnknown(t *testing.T) {
166+
clt, deps := setupClientWithAdmin(t)
167+
ctx := context.Background()
168+
repo := testUniqueRepoName()
169+
const branch = "main"
170+
_, err := deps.catalog.CreateRepository(ctx, repo, "", onBlock(deps, "bucket/prefix"), branch, false)
171+
require.NoError(t, err)
172+
173+
resp, err := clt.UploadObjectPreflightWithResponse(ctx, apigen.RepositoryOwner(repo), apigen.RepositoryName(repo), branch, &apigen.UploadObjectPreflightParams{
174+
Path: "stream.bin",
175+
})
176+
require.NoError(t, err)
177+
require.Equal(t, http.StatusOK, resp.StatusCode())
178+
require.NotNil(t, resp.JSON200)
179+
require.Equal(t, "regular", resp.JSON200.UploadMode)
180+
}
181+
129182
func TestController_LinkXETPhysicalAddressRejectsMissingShard(t *testing.T) {
130183
clt, deps := setupClientWithAdmin(t)
131184
ctx := context.Background()

pkg/config/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,9 @@ type BaseConfig struct {
474474
PrepareInterval time.Duration `mapstructure:"prepare_interval"`
475475
} `mapstructure:"ugc"`
476476
XET struct {
477+
Upload struct {
478+
MinSizeBytes int64 `mapstructure:"min_size_bytes"`
479+
} `mapstructure:"upload"`
477480
Verify struct {
478481
MaxConcurrent int `mapstructure:"max_concurrent"`
479482
} `mapstructure:"verify"`

pkg/config/defaults.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ func setBaseDefaults(cfgType string) {
170170
viper.SetDefault("ugc.prepare_interval", time.Minute)
171171
viper.SetDefault("ugc.prepare_max_file_size", 20*1024*1024)
172172

173+
viper.SetDefault("xet.upload.min_size_bytes", int64(5*1024*1024))
173174
viper.SetDefault("xet.verify.max_concurrent", 0)
174175
viper.SetDefault("xet.read.capability_scan_batch_size", 32)
175176
viper.SetDefault("xet.gc.min_age", 24*time.Hour)

0 commit comments

Comments
 (0)