Skip to content

Commit eab5563

Browse files
committed
feat: support the upload command and optimize the log
Signed-off-by: chlins <[email protected]>
1 parent f54fd22 commit eab5563

File tree

22 files changed

+279
-115
lines changed

22 files changed

+279
-115
lines changed

cmd/modelfile/modelfile.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
package modelfile
1818

1919
import (
20-
"github.com/sirupsen/logrus"
21-
2220
"github.com/spf13/cobra"
2321
"github.com/spf13/viper"
2422
)
@@ -32,8 +30,6 @@ var RootCmd = &cobra.Command{
3230
SilenceUsage: true,
3331
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
3432
RunE: func(cmd *cobra.Command, args []string) error {
35-
logrus.Debug("modctl modelfile is running")
36-
3733
return nil
3834
},
3935
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ require (
2424
github.com/vbauerster/mpb/v8 v8.10.2
2525
golang.org/x/crypto v0.39.0
2626
golang.org/x/sync v0.15.0
27+
golang.org/x/sys v0.33.0
2728
google.golang.org/grpc v1.73.0
2829
oras.land/oras-go/v2 v2.6.0
2930
)
@@ -111,7 +112,6 @@ require (
111112
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
112113
go.uber.org/multierr v1.11.0 // indirect
113114
golang.org/x/net v0.40.0 // indirect
114-
golang.org/x/sys v0.33.0 // indirect
115115
golang.org/x/term v0.32.0 // indirect
116116
golang.org/x/text v0.26.0 // indirect
117117
google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect

pkg/backend/attach.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ var (
6060

6161
// Attach attaches user materials into the model artifact which follows the Model Spec.
6262
func (b *backend) Attach(ctx context.Context, filepath string, cfg *config.Attach) error {
63-
logrus.Infof("attaching file %s, cfg: %+v", filepath, cfg)
63+
logrus.Infof("attach: starting attach operation for file %s [config: %+v]", filepath, cfg)
6464
srcManifest, err := b.getManifest(ctx, cfg.Source, cfg.OutputRemote, cfg.PlainHTTP, cfg.Insecure)
6565
if err != nil {
6666
return fmt.Errorf("failed to get source manifest: %w", err)
@@ -71,7 +71,7 @@ func (b *backend) Attach(ctx context.Context, filepath string, cfg *config.Attac
7171
return fmt.Errorf("failed to get source model config: %w", err)
7272
}
7373

74-
logrus.Infof("source model config: %+v", srcModelConfig)
74+
logrus.Infof("attach: loaded source model config [%+v]", srcModelConfig)
7575

7676
var foundLayer *ocispec.Descriptor
7777
for _, layer := range srcManifest.Layers {
@@ -87,7 +87,7 @@ func (b *backend) Attach(ctx context.Context, filepath string, cfg *config.Attac
8787
}
8888
}
8989

90-
logrus.Infof("found original layer: %+v", foundLayer)
90+
logrus.Infof("attach: found existing layer for file %s [%+v]", filepath, foundLayer)
9191

9292
layers := srcManifest.Layers
9393
if foundLayer != nil {
@@ -123,7 +123,7 @@ func (b *backend) Attach(ctx context.Context, filepath string, cfg *config.Attac
123123
layers = append(layers, newLayers...)
124124
sortLayers(layers)
125125

126-
logrus.Infof("new sorted layers: %+v", layers)
126+
logrus.Debugf("attach: generated sorted layers [layers: %+v]", layers)
127127

128128
diffIDs := []godigest.Digest{}
129129
for _, layer := range layers {
@@ -145,7 +145,7 @@ func (b *backend) Attach(ctx context.Context, filepath string, cfg *config.Attac
145145
Name: srcModelConfig.Descriptor.Name,
146146
}
147147

148-
logrus.Infof("new model config: %+v", modelConfig)
148+
logrus.Infof("attach: built model config [%+v]", modelConfig)
149149

150150
configDesc, err := builder.BuildConfig(ctx, layers, modelConfig, hooks.NewHooks(
151151
hooks.WithOnStart(func(name string, size int64, reader io.Reader) io.Reader {
@@ -178,6 +178,7 @@ func (b *backend) Attach(ctx context.Context, filepath string, cfg *config.Attac
178178
return fmt.Errorf("failed to build model manifest: %w", err)
179179
}
180180

181+
logrus.Infof("attach: successfully attached file %s", filepath)
181182
return nil
182183
}
183184

pkg/backend/build.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const (
4646

4747
// Build builds the user materials into the model artifact which follows the Model Spec.
4848
func (b *backend) Build(ctx context.Context, modelfilePath, workDir, target string, cfg *config.Build) error {
49-
logrus.Infof("building model artifact: %s, cfg: %+v", target, cfg)
49+
logrus.Infof("build: starting build operation for target %s [config: %+v]", target, cfg)
5050
// parse the repo name and tag name from target.
5151
ref, err := ParseReference(target)
5252
if err != nil {
@@ -99,7 +99,7 @@ func (b *backend) Build(ctx context.Context, modelfilePath, workDir, target stri
9999

100100
layers = append(layers, layerDescs...)
101101

102-
logrus.Infof("model artifact layers: %+v", layers)
102+
logrus.Infof("build: processed layers for artifact [count: %d, layers: %+v]", len(layers), layers)
103103

104104
revision := sourceInfo.Commit
105105
if revision != "" && sourceInfo.Dirty {
@@ -118,7 +118,7 @@ func (b *backend) Build(ctx context.Context, modelfilePath, workDir, target stri
118118
SourceRevision: revision,
119119
}
120120

121-
logrus.Infof("model artifact config: %+v", modelConfig)
121+
logrus.Infof("build: built model config [family: %s, name: %s, format: %s]", modelConfig.Family, modelConfig.Name, modelConfig.Format)
122122

123123
var configDesc ocispec.Descriptor
124124
// Build the model config.
@@ -157,6 +157,7 @@ func (b *backend) Build(ctx context.Context, modelfilePath, workDir, target stri
157157
return fmt.Errorf("failed to build model manifest: %w", err)
158158
}
159159

160+
logrus.Infof("build: successfully built model artifact %s", target)
160161
return nil
161162
}
162163

pkg/backend/build/builder.go

Lines changed: 162 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"io"
2626
"os"
2727
"path/filepath"
28+
"strconv"
2829
"sync"
2930
"syscall"
3031
"time"
@@ -35,11 +36,12 @@ import (
3536
spec "github.com/opencontainers/image-spec/specs-go"
3637
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
3738
"github.com/sirupsen/logrus"
39+
"golang.org/x/sys/unix"
3840

3941
buildconfig "github.com/CloudNativeAI/modctl/pkg/backend/build/config"
4042
"github.com/CloudNativeAI/modctl/pkg/backend/build/hooks"
4143
"github.com/CloudNativeAI/modctl/pkg/backend/build/interceptor"
42-
"github.com/CloudNativeAI/modctl/pkg/codec"
44+
pkgcodec "github.com/CloudNativeAI/modctl/pkg/codec"
4345
"github.com/CloudNativeAI/modctl/pkg/storage"
4446
)
4547

@@ -142,43 +144,22 @@ func (ab *abstractBuilder) BuildLayer(ctx context.Context, mediaType, workDir, p
142144
return ocispec.Descriptor{}, fmt.Errorf("failed to get relative path: %w", err)
143145
}
144146

145-
codec, err := codec.New(codec.TypeFromMediaType(mediaType))
147+
codec, err := pkgcodec.New(pkgcodec.TypeFromMediaType(mediaType))
146148
if err != nil {
147149
return ocispec.Descriptor{}, fmt.Errorf("failed to create codec: %w", err)
148150
}
149151

150-
logrus.Infof("building file %s...", relPath)
152+
logrus.Debugf("builder: starting build layer for file %s", relPath)
151153

152154
// Encode the content by codec depends on the media type.
153155
reader, err := codec.Encode(path, workDirPath)
154156
if err != nil {
155157
return ocispec.Descriptor{}, fmt.Errorf("failed to encode file: %w", err)
156158
}
157159

158-
logrus.Infof("calculating digest for %s...", relPath)
159-
// Calculate the digest of the encoded content.
160-
hash := sha256.New()
161-
size, err := io.Copy(hash, reader)
160+
reader, digest, size, err := computeDigestAndSize(mediaType, path, workDirPath, info, reader, codec)
162161
if err != nil {
163-
return ocispec.Descriptor{}, fmt.Errorf("failed to copy content to hash: %w", err)
164-
}
165-
166-
digest := fmt.Sprintf("sha256:%x", hash.Sum(nil))
167-
logrus.Infof("calculated digest for %s: %s", relPath, digest)
168-
169-
// Seek the reader to the beginning if supported,
170-
// otherwise we needs to re-encode the content again.
171-
if seeker, ok := reader.(io.ReadSeeker); ok {
172-
logrus.Infof("seeking %s reader to beginning...", relPath)
173-
if _, err := seeker.Seek(0, io.SeekStart); err != nil {
174-
return ocispec.Descriptor{}, fmt.Errorf("failed to seek reader: %w", err)
175-
}
176-
} else {
177-
logrus.Infof("%s reader is not seekable, re-encoding...", relPath)
178-
reader, err = codec.Encode(path, workDirPath)
179-
if err != nil {
180-
return ocispec.Descriptor{}, fmt.Errorf("failed to encode file: %w", err)
181-
}
162+
return ocispec.Descriptor{}, fmt.Errorf("failed to compute digest and size: %w", err)
182163
}
183164

184165
var (
@@ -213,24 +194,10 @@ func (ab *abstractBuilder) BuildLayer(ctx context.Context, mediaType, workDir, p
213194
applyDesc(&desc)
214195
}
215196

216-
// Retrieve the file metadata.
217-
metadata, err := getFileMetadata(path)
218-
if err != nil {
219-
return desc, fmt.Errorf("failed to retrieve file metadata: %w", err)
220-
}
221-
222-
metadataStr, err := json.Marshal(metadata)
223-
if err != nil {
224-
return desc, fmt.Errorf("failed to marshal metadata: %w", err)
225-
}
226-
227-
logrus.Infof("retrieved file %s metadata: %s", relPath, string(metadataStr))
228-
229-
// Apply the metadata to the descriptor annotation.
230-
if desc.Annotations == nil {
231-
desc.Annotations = make(map[string]string)
197+
// Add file metadata to descriptor.
198+
if err := addFileMetadata(&desc, path, relPath); err != nil {
199+
return desc, err
232200
}
233-
desc.Annotations[modelspec.AnnotationFileMetadata] = string(metadataStr)
234201

235202
return desc, nil
236203
}
@@ -315,6 +282,109 @@ func buildModelConfig(modelConfig *buildconfig.Model, layers []ocispec.Descripto
315282
}, nil
316283
}
317284

285+
// computeDigestAndSize computes the digest and size for the encoded content, using xattrs if available.
286+
func computeDigestAndSize(mediaType, path, workDirPath string, info os.FileInfo, reader io.Reader, codec pkgcodec.Codec) (io.Reader, string, int64, error) {
287+
var digest string
288+
var size int64
289+
290+
if pkgcodec.IsRawMediaType(mediaType) {
291+
// By default let's assume the mtime and size has changed.
292+
mtimeChanged := true
293+
sizeChanged := true
294+
295+
if mtime, err := getXattr(path, xattrMtimeKey(mediaType)); err == nil {
296+
if string(mtime) == fmt.Sprintf("%d", info.ModTime().UnixNano()) {
297+
mtimeChanged = false
298+
}
299+
}
300+
301+
if sizeBytes, err := getXattr(path, xattrSizeKey(mediaType)); err == nil {
302+
if parsedSize, err := strconv.ParseInt(string(sizeBytes), 10, 64); err == nil {
303+
if parsedSize == info.Size() {
304+
sizeChanged = false
305+
}
306+
}
307+
}
308+
309+
if !mtimeChanged && !sizeChanged {
310+
// Check xattrs for cached digest and size.
311+
if sha256, err := getXattr(path, xattrSha256Key(mediaType)); err == nil {
312+
digest = string(sha256)
313+
logrus.Infof("builder: retrieved sha256 hash from xattr for file %s [digest: %s]", path, digest)
314+
}
315+
316+
if sizeBytes, err := getXattr(path, xattrSizeKey(mediaType)); err == nil {
317+
if parsedSize, err := strconv.ParseInt(string(sizeBytes), 10, 64); err == nil {
318+
size = parsedSize
319+
logrus.Infof("builder: retrieved size from xattr for file %s [size: %d]", path, size)
320+
}
321+
}
322+
}
323+
}
324+
325+
// Compute digest and size if not retrieved from xattrs.
326+
if digest == "" {
327+
logrus.Infof("builder: calculating digest for file %s", path)
328+
var err error
329+
hash := sha256.New()
330+
size, err = io.Copy(hash, reader)
331+
if err != nil {
332+
return reader, "", 0, fmt.Errorf("failed to copy content to hash: %w", err)
333+
}
334+
digest = fmt.Sprintf("sha256:%x", hash.Sum(nil))
335+
logrus.Infof("builder: calculated digest for file %s [digest: %s]", path, digest)
336+
337+
// Reset reader
338+
reader, err = resetReader(reader, path, workDirPath, codec)
339+
if err != nil {
340+
return reader, "", 0, err
341+
}
342+
343+
// Store xattrs if raw media type.
344+
if pkgcodec.IsRawMediaType(mediaType) {
345+
setXattr(path, xattrMtimeKey(mediaType), fmt.Appendf([]byte{}, "%d", info.ModTime().UnixNano()))
346+
setXattr(path, xattrSha256Key(mediaType), []byte(digest))
347+
setXattr(path, xattrSizeKey(mediaType), fmt.Appendf([]byte{}, "%d", size))
348+
}
349+
}
350+
351+
return reader, digest, size, nil
352+
}
353+
354+
// resetReader resets the reader to the beginning or re-encodes if not seekable.
355+
func resetReader(reader io.Reader, path, workDirPath string, codec pkgcodec.Codec) (io.Reader, error) {
356+
if seeker, ok := reader.(io.ReadSeeker); ok {
357+
logrus.Debugf("builder: seeking reader to beginning for file %s", path)
358+
if _, err := seeker.Seek(0, io.SeekStart); err != nil {
359+
return nil, fmt.Errorf("failed to seek reader: %w", err)
360+
}
361+
return reader, nil
362+
}
363+
364+
logrus.Debugf("builder: reader not seekable, re-encoding file %s", path)
365+
return codec.Encode(path, workDirPath)
366+
}
367+
368+
// addFileMetadata adds file metadata to the descriptor.
369+
func addFileMetadata(desc *ocispec.Descriptor, path, relPath string) error {
370+
metadata, err := getFileMetadata(path)
371+
if err != nil {
372+
return fmt.Errorf("failed to retrieve file metadata: %w", err)
373+
}
374+
375+
metadataStr, err := json.Marshal(metadata)
376+
if err != nil {
377+
return fmt.Errorf("failed to marshal metadata: %w", err)
378+
}
379+
logrus.Infof("builder: retrieved metadata for file %s [metadata: %s]", relPath, string(metadataStr))
380+
381+
if desc.Annotations == nil {
382+
desc.Annotations = make(map[string]string)
383+
}
384+
desc.Annotations[modelspec.AnnotationFileMetadata] = string(metadataStr)
385+
return nil
386+
}
387+
318388
// splitReader splits the original reader into two readers.
319389
func splitReader(original io.Reader) (io.Reader, io.Reader) {
320390
r1, w1 := io.Pipe()
@@ -368,3 +438,52 @@ func getFileMetadata(path string) (modelspec.FileMetadata, error) {
368438

369439
return metadata, nil
370440
}
441+
442+
func xattrSha256Key(mediaType string) string {
443+
// Uniformity between linux and mac platforms is simplified by adding the prefix 'user.',
444+
// because the key may be unlimited under mac,
445+
// but on linux, in some cases, the user can only manipulate the user space.
446+
return fmt.Sprintf("user.%s.sha256", mediaType)
447+
}
448+
449+
func xattrSizeKey(mediaType string) string {
450+
// Uniformity between linux and mac platforms is simplified by adding the prefix 'user.',
451+
// because the key may be unlimited under mac,
452+
// but on linux, in some cases, the user can only manipulate the user space.
453+
return fmt.Sprintf("user.%s.size", mediaType)
454+
}
455+
456+
func xattrMtimeKey(mediaType string) string {
457+
// Uniformity between linux and mac platforms is simplified by adding the prefix 'user.',
458+
// because the key may be unlimited under mac,
459+
// but on linux, in some cases, the user can only manipulate the user space.
460+
return fmt.Sprintf("user.%s.mtime", mediaType)
461+
}
462+
463+
// getXattr retrieves an xattr value for a given key.
464+
func getXattr(path, key string) ([]byte, error) {
465+
var value []byte
466+
sz, err := unix.Getxattr(path, key, value)
467+
if err != nil {
468+
logrus.Warnf("builder: failed to get xattr %s for file %s: %v", key, path, err)
469+
return nil, err
470+
}
471+
472+
value = make([]byte, sz)
473+
_, err = unix.Getxattr(path, key, value)
474+
if err != nil {
475+
logrus.Warnf("builder: failed to get xattr %s for file %s: %v", key, path, err)
476+
return nil, err
477+
}
478+
479+
return value, nil
480+
}
481+
482+
// setXattr sets an xattr value for a given key.
483+
func setXattr(path, key string, value []byte) {
484+
if err := unix.Setxattr(path, key, value, 0); err != nil {
485+
logrus.Warnf("builder: failed to set xattr %s for file %s: %v", key, path, err)
486+
} else {
487+
logrus.Infof("builder: set xattr %s for file %s: %s", key, path, string(value))
488+
}
489+
}

pkg/backend/build/remote.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ func (ro *remoteOutput) OutputLayer(ctx context.Context, mediaType, relPath, dig
7070

7171
if exist {
7272
// In case the reader is from PipeReader, we need to read the whole reader to avoid the pipe being blocked.
73-
io.Copy(io.Discard, reader)
73+
if _, ok := reader.(*io.PipeReader); ok {
74+
io.Copy(io.Discard, reader)
75+
}
76+
7477
hooks.OnComplete(relPath, desc)
7578
return desc, nil
7679
}

0 commit comments

Comments
 (0)