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  }