github.com/whamcloud/lemur@v0.0.0-20190827193804-4655df8a52af/cmd/lhsm-plugin-s3/main.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 "os" 10 "os/signal" 11 "path" 12 "strings" 13 "syscall" 14 "time" 15 16 "github.com/aws/aws-sdk-go/aws" 17 "github.com/aws/aws-sdk-go/aws/credentials" 18 "github.com/aws/aws-sdk-go/aws/defaults" 19 "github.com/aws/aws-sdk-go/aws/session" 20 "github.com/aws/aws-sdk-go/service/s3" 21 "github.com/aws/aws-sdk-go/service/s3/s3manager" 22 "github.com/dustin/go-humanize" 23 "github.com/pkg/errors" 24 "github.com/rcrowley/go-metrics" 25 26 "github.com/intel-hpdd/lemur/dmplugin" 27 "github.com/intel-hpdd/lemur/pkg/fsroot" 28 "github.com/intel-hpdd/logging/alert" 29 "github.com/intel-hpdd/logging/audit" 30 "github.com/intel-hpdd/logging/debug" 31 ) 32 33 type ( 34 archiveConfig struct { 35 Name string `hcl:",key"` 36 ID int 37 AWSAccessKeyID string `hcl:"aws_access_key_id"` 38 AWSSecretAccessKey string `hcl:"aws_secret_access_key"` 39 Endpoint string 40 Region string 41 Bucket string 42 Prefix string 43 UploadPartSize int64 `hcl:"upload_part_size"` 44 45 s3Creds *credentials.Credentials 46 } 47 48 archiveSet []*archiveConfig 49 50 s3Config struct { 51 NumThreads int `hcl:"num_threads"` 52 AWSAccessKeyID string `hcl:"aws_access_key_id"` 53 AWSSecretAccessKey string `hcl:"aws_secret_access_key"` 54 Endpoint string `hcl:"endpoint"` 55 Region string `hcl:"region"` 56 UploadPartSize int64 `hcl:"upload_part_size"` 57 Archives archiveSet `hcl:"archive"` 58 } 59 ) 60 61 // Should this be configurable? 62 const updateInterval = 10 * time.Second 63 64 var rate metrics.Meter 65 66 func (c *s3Config) String() string { 67 return dmplugin.DisplayConfig(c) 68 } 69 70 func (a *archiveConfig) String() string { 71 return fmt.Sprintf("%d:%s:%s:%s/%s", a.ID, a.Endpoint, a.Region, a.Bucket, a.Prefix) 72 } 73 74 func (a *archiveConfig) checkValid() error { 75 var errors []string 76 77 if a.Bucket == "" { 78 errors = append(errors, fmt.Sprintf("Archive %s: bucket not set", a.Name)) 79 } 80 81 if a.ID < 1 { 82 errors = append(errors, fmt.Sprintf("Archive %s: archive id not set", a.Name)) 83 84 } 85 86 if a.UploadPartSize < s3manager.MinUploadPartSize { 87 errors = append(errors, fmt.Sprintf("Archive %s: upload_part_size %d is less than minimum (%d)", a.Name, a.UploadPartSize, s3manager.MinUploadPartSize)) 88 } 89 90 if len(errors) > 0 { 91 return fmt.Errorf("Errors: %s", strings.Join(errors, ", ")) 92 } 93 94 return nil 95 } 96 97 func (a *archiveConfig) checkS3Access() error { 98 if _, err := a.s3Creds.Get(); err != nil { 99 return errors.Wrap(err, "No S3 credentials found; cannot initialize data mover") 100 } 101 102 if _, err := s3Svc(a).ListObjects(&s3.ListObjectsInput{ 103 Bucket: aws.String(a.Bucket), 104 }); err != nil { 105 return errors.Wrap(err, "Unable to list S3 bucket objects") 106 } 107 108 return nil 109 } 110 111 // this does an in-place merge, replacing any unset archive-level value 112 // with the global value for that setting 113 func (a *archiveConfig) mergeGlobals(g *s3Config) { 114 if a.AWSAccessKeyID == "" { 115 a.AWSAccessKeyID = g.AWSAccessKeyID 116 } 117 118 if a.AWSSecretAccessKey == "" { 119 a.AWSSecretAccessKey = g.AWSSecretAccessKey 120 } 121 122 if a.Endpoint == "" { 123 a.Endpoint = g.Endpoint 124 } 125 126 if a.Region == "" { 127 a.Region = g.Region 128 } 129 130 if a.UploadPartSize == 0 { 131 a.UploadPartSize = g.UploadPartSize 132 } else { 133 // Allow this to be configured in MiB 134 a.UploadPartSize *= 1024 * 1024 135 } 136 137 // Set the default credentials provider 138 a.s3Creds = defaults.CredChain( 139 aws.NewConfig().WithRegion(a.Region), defaults.Handlers(), 140 ) 141 // If these were set on a per-archive basis, override the defaults. 142 if a.AWSAccessKeyID != "" && a.AWSSecretAccessKey != "" { 143 a.s3Creds = credentials.NewStaticCredentials( 144 a.AWSAccessKeyID, a.AWSSecretAccessKey, "", 145 ) 146 } 147 } 148 149 func (c *s3Config) Merge(other *s3Config) *s3Config { 150 result := new(s3Config) 151 152 result.UploadPartSize = c.UploadPartSize 153 if other.UploadPartSize > 0 { 154 result.UploadPartSize = other.UploadPartSize 155 } 156 157 result.NumThreads = c.NumThreads 158 if other.NumThreads > 0 { 159 result.NumThreads = other.NumThreads 160 } 161 162 result.Region = c.Region 163 if other.Region != "" { 164 result.Region = other.Region 165 } 166 167 result.Endpoint = c.Endpoint 168 if other.Endpoint != "" { 169 result.Endpoint = other.Endpoint 170 } 171 172 result.AWSAccessKeyID = c.AWSAccessKeyID 173 if other.AWSAccessKeyID != "" { 174 result.AWSAccessKeyID = other.AWSAccessKeyID 175 } 176 177 result.AWSSecretAccessKey = c.AWSSecretAccessKey 178 if other.AWSSecretAccessKey != "" { 179 result.AWSSecretAccessKey = other.AWSSecretAccessKey 180 } 181 182 result.Archives = c.Archives 183 if len(other.Archives) > 0 { 184 result.Archives = other.Archives 185 } 186 187 return result 188 } 189 190 func init() { 191 rate = metrics.NewMeter() 192 193 // if debug.Enabled() { 194 go func() { 195 var lastCount int64 196 for { 197 if lastCount != rate.Count() { 198 audit.Logf("total %s (1 min/5 min/15 min/inst): %s/%s/%s/%s msg/sec\n", 199 humanize.Comma(rate.Count()), 200 humanize.Comma(int64(rate.Rate1())), 201 humanize.Comma(int64(rate.Rate5())), 202 humanize.Comma(int64(rate.Rate15())), 203 humanize.Comma(int64(rate.RateMean())), 204 ) 205 lastCount = rate.Count() 206 } 207 time.Sleep(10 * time.Second) 208 } 209 }() 210 // } 211 } 212 213 func s3Svc(ac *archiveConfig) *s3.S3 { 214 cfg := aws.NewConfig().WithRegion(ac.Region).WithCredentials(ac.s3Creds) 215 if debug.Enabled() { 216 cfg.WithLogger(debug.Writer()) 217 cfg.WithLogLevel(aws.LogDebug) 218 } 219 if ac.Endpoint != "" { 220 cfg.WithEndpoint(ac.Endpoint) 221 cfg.WithS3ForcePathStyle(true) 222 } 223 return s3.New(session.New(cfg)) 224 } 225 226 func getMergedConfig(plugin *dmplugin.Plugin) (*s3Config, error) { 227 baseCfg := &s3Config{ 228 Region: "us-east-1", 229 UploadPartSize: s3manager.DefaultUploadPartSize, 230 } 231 232 var cfg s3Config 233 err := dmplugin.LoadConfig(plugin.ConfigFile(), &cfg) 234 235 if err != nil { 236 return nil, fmt.Errorf("Failed to load config: %s", err) 237 } 238 239 // Allow this to be configured in MiB 240 if cfg.UploadPartSize != 0 { 241 cfg.UploadPartSize *= 1024 * 1024 242 } 243 244 return baseCfg.Merge(&cfg), nil 245 } 246 247 func main() { 248 plugin, err := dmplugin.New(path.Base(os.Args[0]), func(path string) (fsroot.Client, error) { 249 return fsroot.New(path) 250 }) 251 if err != nil { 252 alert.Abort(errors.Wrap(err, "failed to initialize plugin")) 253 } 254 defer plugin.Close() 255 256 cfg, err := getMergedConfig(plugin) 257 if err != nil { 258 alert.Abort(errors.Wrap(err, "Unable to determine plugin configuration")) 259 } 260 261 if len(cfg.Archives) == 0 { 262 alert.Abort(errors.New("Invalid configuration: No archives defined")) 263 } 264 265 for _, ac := range cfg.Archives { 266 ac.mergeGlobals(cfg) 267 if err = ac.checkValid(); err != nil { 268 alert.Abort(errors.Wrap(err, "Invalid configuration")) 269 } 270 if err = ac.checkS3Access(); err != nil { 271 alert.Abort(errors.Wrap(err, "S3 access check failed")) 272 } 273 } 274 275 debug.Printf("S3Mover configuration:\n%v", cfg) 276 277 // All base filesystem operations will be relative to current directory 278 err = os.Chdir(plugin.Base()) 279 if err != nil { 280 alert.Abort(errors.Wrap(err, "chdir failed")) 281 } 282 283 interruptHandler(func() { 284 plugin.Stop() 285 }) 286 287 for _, ac := range cfg.Archives { 288 plugin.AddMover(&dmplugin.Config{ 289 Mover: S3Mover(ac, s3Svc(ac), uint32(ac.ID)), 290 NumThreads: cfg.NumThreads, 291 ArchiveID: uint32(ac.ID), 292 }) 293 } 294 295 plugin.Run() 296 } 297 298 func interruptHandler(once func()) { 299 c := make(chan os.Signal, 1) 300 signal.Notify(c, os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM) 301 302 go func() { 303 stopping := false 304 for sig := range c { 305 debug.Printf("signal received: %s", sig) 306 if !stopping { 307 stopping = true 308 once() 309 } 310 } 311 }() 312 }