github.com/whamcloud/lemur@v0.0.0-20190827193804-4655df8a52af/cmd/lhsm-plugin-s3/mover.go (about) 1 // Copyright (c) 2018 DDN. All rights reserved. 2 // Use of this source code is governed by a MIT-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "fmt" 9 "net/url" 10 "path" 11 "time" 12 13 "github.com/aws/aws-sdk-go/aws" 14 "github.com/aws/aws-sdk-go/service/s3" 15 "github.com/aws/aws-sdk-go/service/s3/s3manager" 16 "github.com/pkg/errors" 17 18 "github.com/intel-hpdd/lemur/dmplugin" 19 "github.com/intel-hpdd/lemur/dmplugin/dmio" 20 "github.com/intel-hpdd/logging/debug" 21 "github.com/pborman/uuid" 22 ) 23 24 // Mover is an S3 data mover 25 type Mover struct { 26 name string 27 s3Svc *s3.S3 28 cfg *archiveConfig 29 } 30 31 // S3Mover returns a new *Mover 32 func S3Mover(cfg *archiveConfig, s3Svc *s3.S3, archiveID uint32) *Mover { 33 return &Mover{ 34 name: fmt.Sprintf("s3-%d", archiveID), 35 s3Svc: s3Svc, 36 cfg: cfg, 37 } 38 } 39 40 func newFileID() string { 41 return uuid.New() 42 } 43 44 func (m *Mover) destination(id string) string { 45 return path.Join(m.cfg.Prefix, 46 "o", 47 id) 48 } 49 50 func (m *Mover) newUploader() *s3manager.Uploader { 51 // can configure stuff here with custom setters 52 var partSize = func(u *s3manager.Uploader) { 53 u.PartSize = m.cfg.UploadPartSize 54 } 55 return s3manager.NewUploaderWithClient(m.s3Svc, partSize) 56 57 } 58 59 func (m *Mover) newDownloader() *s3manager.Downloader { 60 return s3manager.NewDownloaderWithClient(m.s3Svc) 61 } 62 63 // Start signals the mover to begin any asynchronous processing (e.g. stats) 64 func (m *Mover) Start() { 65 debug.Printf("%s started", m.name) 66 } 67 func (m *Mover) fileIDtoBucketPath(fileID string) (string, string, error) { 68 var bucket, path string 69 70 u, err := url.ParseRequestURI(fileID) 71 if err == nil { 72 if u.Scheme != "s3" { 73 return "", "", errors.Errorf("invalid URL in file_id %s", fileID) 74 } 75 path = u.Path 76 bucket = u.Host 77 } else { 78 path = m.destination(fileID) 79 bucket = m.cfg.Bucket 80 } 81 debug.Printf("Parsed %s -> %s/%s", fileID, bucket, path) 82 return bucket, path, nil 83 } 84 85 // Archive fulfills an HSM Archive request 86 func (m *Mover) Archive(action dmplugin.Action) error { 87 debug.Printf("%s id:%d archive %s %s", m.name, action.ID(), action.PrimaryPath(), action.UUID()) 88 rate.Mark(1) 89 start := time.Now() 90 91 fileID := newFileID() 92 fileKey := m.destination(fileID) 93 94 rdr, total, err := dmio.NewActionReader(action) 95 if err != nil { 96 return errors.Wrapf(err, "Could not create archive reader for %s", action) 97 } 98 defer rdr.Close() 99 100 progressFunc := func(offset, length int64) error { 101 return action.Update(offset, length, total) 102 } 103 progressReader := dmio.NewProgressReader(rdr, updateInterval, progressFunc) 104 defer progressReader.StopUpdates() 105 106 uploader := m.newUploader() 107 out, err := uploader.Upload(&s3manager.UploadInput{ 108 Body: progressReader, 109 Bucket: aws.String(m.cfg.Bucket), 110 Key: aws.String(fileKey), 111 ContentType: aws.String("application/octet-stream"), 112 }) 113 if err != nil { 114 if multierr, ok := err.(s3manager.MultiUploadFailure); ok { 115 return errors.Errorf("Upload error on %s: %s (%s)", multierr.UploadID(), multierr.Code(), multierr.Message()) 116 } 117 return errors.Wrap(err, "upload failed") 118 } 119 120 debug.Printf("%s id:%d Archived %d bytes in %v from %s to %s", m.name, action.ID(), total, 121 time.Since(start), 122 action.PrimaryPath(), 123 out.Location) 124 125 u := url.URL{ 126 Scheme: "s3", 127 Host: m.cfg.Bucket, 128 Path: fileKey, 129 } 130 131 action.SetUUID(fileID) 132 action.SetURL(u.String()) 133 action.SetActualLength(total) 134 return nil 135 } 136 137 // Restore fulfills an HSM Restore request 138 func (m *Mover) Restore(action dmplugin.Action) error { 139 debug.Printf("%s id:%d restore %s %s", m.name, action.ID(), action.PrimaryPath(), action.UUID()) 140 rate.Mark(1) 141 142 start := time.Now() 143 if action.UUID() == "" { 144 return errors.Errorf("Missing file_id on action %d", action.ID()) 145 } 146 bucket, srcObj, err := m.fileIDtoBucketPath(action.UUID()) 147 if err != nil { 148 return errors.Wrap(err, "fileIDtoBucketPath failed") 149 } 150 out, err := m.s3Svc.HeadObject(&s3.HeadObjectInput{ 151 Bucket: aws.String(bucket), 152 Key: aws.String(srcObj), 153 }) 154 155 if err != nil { 156 return errors.Wrapf(err, "s3.HeadObject() on %s failed", srcObj) 157 } 158 debug.Printf("obj %s, size %d", srcObj, *out.ContentLength) 159 160 dstSize := *out.ContentLength 161 dst, err := dmio.NewActionWriter(action) 162 if err != nil { 163 return errors.Wrapf(err, "Couldn't create ActionWriter for %s", action) 164 } 165 defer dst.Close() 166 167 progressFunc := func(offset, length int64) error { 168 return action.Update(offset, length, dstSize) 169 } 170 progressWriter := dmio.NewProgressWriterAt(dst, updateInterval, progressFunc) 171 defer progressWriter.StopUpdates() 172 173 downloader := m.newDownloader() 174 n, err := downloader.Download(progressWriter, 175 &s3.GetObjectInput{ 176 Bucket: aws.String(m.cfg.Bucket), 177 Key: aws.String(srcObj), 178 }) 179 if err != nil { 180 return errors.Errorf("s3.Download() of %s failed: %s", srcObj, err) 181 } 182 183 debug.Printf("%s id:%d Restored %d bytes in %v from %s to %s", m.name, action.ID(), n, 184 time.Since(start), 185 srcObj, 186 action.PrimaryPath()) 187 action.SetActualLength(n) 188 return nil 189 } 190 191 // Remove fulfills an HSM Remove request 192 func (m *Mover) Remove(action dmplugin.Action) error { 193 debug.Printf("%s id:%d remove %s %s", m.name, action.ID(), action.PrimaryPath(), action.UUID()) 194 rate.Mark(1) 195 if action.UUID() == "" { 196 return errors.New("Missing file_id") 197 } 198 199 bucket, srcObj, err := m.fileIDtoBucketPath(string(action.UUID())) 200 201 _, err = m.s3Svc.DeleteObject(&s3.DeleteObjectInput{ 202 Bucket: aws.String(bucket), 203 Key: aws.String(srcObj), 204 }) 205 return errors.Wrap(err, "delete object failed") 206 }