github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ais/s3/mpt.go (about)

     1  // Package s3 provides Amazon S3 compatibility layer
     2  /*
     3   * Copyright (c) 2018-2022, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package s3
     6  
     7  import (
     8  	"fmt"
     9  	"net/http"
    10  	"os"
    11  	"sort"
    12  	"strconv"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/NVIDIA/aistore/cmn/debug"
    17  	"github.com/NVIDIA/aistore/cmn/nlog"
    18  	"github.com/NVIDIA/aistore/core"
    19  )
    20  
    21  // NOTE: xattr stores only the (*) marked attributes
    22  type (
    23  	MptPart struct {
    24  		MD5  string // MD5 of the part (*)
    25  		FQN  string // FQN of the corresponding workfile
    26  		Size int64  // part size in bytes (*)
    27  		Num  int32  // part number (*)
    28  	}
    29  	mpt struct {
    30  		bckName string
    31  		objName string
    32  		parts   []*MptPart // by part number
    33  		ctime   time.Time  // InitUpload time
    34  	}
    35  	uploads map[string]*mpt // by upload ID
    36  )
    37  
    38  var (
    39  	ups uploads
    40  	mu  sync.RWMutex
    41  )
    42  
    43  // Start miltipart upload
    44  func InitUpload(id, bckName, objName string) {
    45  	mu.Lock()
    46  	if ups == nil {
    47  		ups = make(uploads, 8)
    48  	}
    49  	ups[id] = &mpt{
    50  		bckName: bckName,
    51  		objName: objName,
    52  		parts:   make([]*MptPart, 0, iniCapParts),
    53  		ctime:   time.Now(),
    54  	}
    55  	mu.Unlock()
    56  }
    57  
    58  // Add part to an active upload.
    59  // Some clients may omit size and md5. Only partNum is must-have.
    60  // md5 and fqn is filled by a target after successful saving the data to a workfile.
    61  func AddPart(id string, npart *MptPart) (err error) {
    62  	mu.Lock()
    63  	mpt, ok := ups[id]
    64  	if !ok {
    65  		err = fmt.Errorf("upload %q not found (%s, %d)", id, npart.FQN, npart.Num)
    66  	} else {
    67  		mpt.parts = append(mpt.parts, npart)
    68  	}
    69  	mu.Unlock()
    70  	return
    71  }
    72  
    73  // TODO: compare non-zero sizes (note: s3cmd sends 0) and part.ETag as well, if specified
    74  func CheckParts(id string, parts []*PartInfo) ([]*MptPart, error) {
    75  	mu.RLock()
    76  	defer mu.RUnlock()
    77  	mpt, ok := ups[id]
    78  	if !ok {
    79  		return nil, fmt.Errorf("upload %q not found", id)
    80  	}
    81  	// first, check that all parts are present
    82  	var prev = int32(-1)
    83  	for _, part := range parts {
    84  		debug.Assert(part.PartNumber > prev) // must ascend
    85  		if mpt.getPart(part.PartNumber) == nil {
    86  			return nil, fmt.Errorf("upload %q: part %d not found", id, part.PartNumber)
    87  		}
    88  		prev = part.PartNumber
    89  	}
    90  	// copy (to work on it with no locks)
    91  	nparts := make([]*MptPart, 0, len(parts))
    92  	for _, part := range parts {
    93  		nparts = append(nparts, mpt.getPart(part.PartNumber))
    94  	}
    95  	return nparts, nil
    96  }
    97  
    98  func ParsePartNum(s string) (int32, error) {
    99  	partNum, err := strconv.ParseInt(s, 10, 32)
   100  	if err != nil {
   101  		err = fmt.Errorf("invalid part number %q (must be in 1-%d range): %v", s, MaxPartsPerUpload, err)
   102  	}
   103  	return int32(partNum), err
   104  }
   105  
   106  // Return a sum of upload part sizes.
   107  // Used on upload completion to calculate the final size of the object.
   108  func ObjSize(id string) (size int64, err error) {
   109  	mu.RLock()
   110  	mpt, ok := ups[id]
   111  	if !ok {
   112  		err = fmt.Errorf("upload %q not found", id)
   113  	} else {
   114  		for _, part := range mpt.parts {
   115  			size += part.Size
   116  		}
   117  	}
   118  	mu.RUnlock()
   119  	return
   120  }
   121  
   122  // remove all temp files and delete from the map
   123  // if completed (i.e., not aborted): store xattr
   124  func CleanupUpload(id, fqn string, aborted bool) (exists bool) {
   125  	mu.Lock()
   126  	mpt, ok := ups[id]
   127  	if !ok {
   128  		mu.Unlock()
   129  		nlog.Warningf("fqn %s, id %s", fqn, id)
   130  		return false
   131  	}
   132  	delete(ups, id)
   133  	mu.Unlock()
   134  
   135  	if !aborted {
   136  		if err := storeMptXattr(fqn, mpt); err != nil {
   137  			nlog.Warningf("fqn %s, id %s: %v", fqn, id, err)
   138  		}
   139  	}
   140  	for _, part := range mpt.parts {
   141  		if err := os.Remove(part.FQN); err != nil && !os.IsNotExist(err) {
   142  			nlog.Errorln(err)
   143  		}
   144  	}
   145  	return true
   146  }
   147  
   148  func ListUploads(bckName, idMarker string, maxUploads int) (result *ListMptUploadsResult) {
   149  	mu.RLock()
   150  	results := make([]UploadInfoResult, 0, len(ups))
   151  	for id, mpt := range ups {
   152  		results = append(results, UploadInfoResult{Key: mpt.objName, UploadID: id, Initiated: mpt.ctime})
   153  	}
   154  	mu.RUnlock()
   155  
   156  	sort.Slice(results, func(i int, j int) bool {
   157  		return results[i].Initiated.Before(results[j].Initiated)
   158  	})
   159  
   160  	var from int
   161  	if idMarker != "" {
   162  		// truncate
   163  		for i, res := range results {
   164  			if res.UploadID == idMarker {
   165  				from = i + 1
   166  				break
   167  			}
   168  			copy(results, results[from:])
   169  			results = results[:len(results)-from]
   170  		}
   171  	}
   172  	if maxUploads > 0 && len(results) > maxUploads {
   173  		results = results[:maxUploads]
   174  	}
   175  	result = &ListMptUploadsResult{Bucket: bckName, Uploads: results, IsTruncated: from > 0}
   176  	return
   177  }
   178  
   179  func ListParts(id string, lom *core.LOM) (parts []*PartInfo, ecode int, err error) {
   180  	mu.RLock()
   181  	mpt, ok := ups[id]
   182  	if !ok {
   183  		ecode = http.StatusNotFound
   184  		mpt, err = loadMptXattr(lom.FQN)
   185  		if err != nil || mpt == nil {
   186  			mu.RUnlock()
   187  			return nil, ecode, err
   188  		}
   189  		mpt.bckName, mpt.objName = lom.Bck().Name, lom.ObjName
   190  		mpt.ctime = lom.Atime()
   191  	}
   192  	parts = make([]*PartInfo, 0, len(mpt.parts))
   193  	for _, part := range mpt.parts {
   194  		parts = append(parts, &PartInfo{ETag: part.MD5, PartNumber: part.Num, Size: part.Size})
   195  	}
   196  	mu.RUnlock()
   197  	return parts, ecode, err
   198  }