// Copyright 2019 Yunion // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package models import ( "context" "fmt" "io" "os" "strings" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/qemuimgfmt" "yunion.io/x/onecloud/pkg/apis/image" "yunion.io/x/onecloud/pkg/image/drivers/s3" "yunion.io/x/onecloud/pkg/image/options" "yunion.io/x/onecloud/pkg/util/fileutils2" "yunion.io/x/onecloud/pkg/util/procutils" ) var local IImageStorage = &LocalStorage{} var s3Instance IImageStorage = &S3Storage{} var storage IImageStorage func GetStorage() IImageStorage { return storage } func GetImage(ctx context.Context, location string) (int64, io.ReadCloser, error) { switch { case strings.HasPrefix(location, image.S3Prefix): return s3Instance.GetImage(ctx, location[len(image.S3Prefix):]) case strings.HasPrefix(location, image.LocalFilePrefix): return local.GetImage(ctx, location[len(image.LocalFilePrefix):]) default: return local.GetImage(ctx, location) } } func RemoveImage(ctx context.Context, location string) error { switch { case strings.HasPrefix(location, image.S3Prefix): return s3Instance.RemoveImage(ctx, location[len(image.S3Prefix):]) case strings.HasPrefix(location, image.LocalFilePrefix): return local.RemoveImage(ctx, location[len(image.LocalFilePrefix):]) default: return local.RemoveImage(ctx, location) } } func IsCheckStatusEnabled(img *SImage) bool { switch { case strings.HasPrefix(img.Location, image.S3Prefix): return s3Instance.IsCheckStatusEnabled() case strings.HasPrefix(img.Location, image.LocalFilePrefix): return local.IsCheckStatusEnabled() default: return local.IsCheckStatusEnabled() } } func Init(storageBackend string) { switch storageBackend { case image.IMAGE_STORAGE_DRIVER_LOCAL: storage = &LocalStorage{} case image.IMAGE_STORAGE_DRIVER_S3: storage = &S3Storage{} default: storage = &LocalStorage{} } } type IImageStorage interface { Type() string SaveImage(context.Context, string, func(int64)) (string, error) CleanTempfile(string) error GetImage(context.Context, string) (int64, io.ReadCloser, error) RemoveImage(context.Context, string) error IsCheckStatusEnabled() bool ConvertImage(ctx context.Context, image *SImage, targetFormat string, progresser func(saved int64)) (*SConverImageInfo, error) } type LocalStorage struct{} func (s *LocalStorage) Type() string { return image.IMAGE_STORAGE_DRIVER_LOCAL } func (s *LocalStorage) SaveImage(ctx context.Context, imagePath string, progresser func(saved int64)) (string, error) { return fmt.Sprintf("%s%s", LocalFilePrefix, imagePath), nil } func (s *LocalStorage) CleanTempfile(filePath string) error { return nil } func (s *LocalStorage) GetImage(ctx context.Context, imagePath string) (int64, io.ReadCloser, error) { fstat, err := os.Stat(imagePath) if err != nil { return -1, nil, errors.Wrapf(err, "stat file %s", imagePath) } f, err := os.Open(imagePath) if err != nil { return -1, nil, errors.Wrapf(err, "open file %s", imagePath) } return fstat.Size(), f, nil } func (s *LocalStorage) ConvertImage(ctx context.Context, image *SImage, targetFormat string, progresser func(saved int64)) (*SConverImageInfo, error) { location := image.GetPath(targetFormat) img, err := image.getQemuImage() if err != nil { return nil, errors.Wrap(err, "unable to image.getQemuImage") } nimg, err := img.Clone(location, qemuimgfmt.String2ImageFormat(targetFormat), true) if err != nil { return nil, errors.Wrap(err, "unable to img.Clone") } return &SConverImageInfo{ Location: fmt.Sprintf("%s%s", LocalFilePrefix, location), SizeBytes: nimg.ActualSizeBytes, }, nil } func (s *LocalStorage) IsCheckStatusEnabled() bool { return true } func (s *LocalStorage) RemoveImage(ctx context.Context, imagePath string) error { return os.Remove(imagePath) } type S3Storage struct{} func imagePathToName(imagePath string) string { segs := strings.Split(imagePath, "/") return segs[len(segs)-1] } func (s *S3Storage) Type() string { return image.IMAGE_STORAGE_DRIVER_S3 } func (s *S3Storage) SaveImage(ctx context.Context, imagePath string, progresser func(saved int64)) (string, error) { if !fileutils2.IsFile(imagePath) { return "", fmt.Errorf("%s not valid file", imagePath) } return s3.Put(ctx, imagePath, imagePathToName(imagePath), options.Options.S3UploadPartSizeMb, options.Options.S3UploadParallel, progresser) } func (s *S3Storage) CleanTempfile(filePath string) error { out, err := procutils.NewCommand("rm", "-f", filePath).Output() if err != nil { return errors.Wrapf(err, "rm %s failed %s", filePath, out) } return nil } func (s *S3Storage) getTempDir() (string, error) { var dir string if options.Options.FilesystemStoreDatadir != "" { dir = options.Options.FilesystemStoreDatadir + "/image-tmp" } else { dir = "/tmp/image-tmp" } if !fileutils2.Exists(dir) { err := procutils.NewCommand("mkdir", "-p", dir).Run() if err != nil { return "", errors.Wrapf(err, "unable to create dir %s", dir) } } return dir, nil } type SConverImageInfo struct { Location string SizeBytes int64 } func (s *S3Storage) ConvertImage(ctx context.Context, image *SImage, targetFormat string, progresser func(saved int64)) (*SConverImageInfo, error) { tempDir, err := s.getTempDir() if err != nil { return nil, err } location := fmt.Sprintf("%s/%s.%s", tempDir, image.GetId(), targetFormat) img, err := image.getQemuImage() if err != nil { return nil, errors.Wrap(err, "unable to image.getQemuImage") } nimg, err := img.Clone(location, qemuimgfmt.String2ImageFormat(targetFormat), true) if err != nil { return nil, errors.Wrap(err, "unable to img.Clone") } defer s.CleanTempfile(location) s3Location, err := s.SaveImage(ctx, location, progresser) if err != nil { return nil, errors.Wrap(err, "unable to SaveImage") } return &SConverImageInfo{ Location: s3Location, SizeBytes: nimg.ActualSizeBytes, }, nil } func (s *S3Storage) GetImage(ctx context.Context, imagePath string) (int64, io.ReadCloser, error) { return s3.Get(ctx, imagePathToName(imagePath)) } func (s *S3Storage) IsCheckStatusEnabled() bool { return options.Options.S3CheckImageStatus } func (s *S3Storage) RemoveImage(ctx context.Context, fileName string) error { return s3.Remove(ctx, fileName) }