yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/azure/storagecache.go (about)

     1  // Copyright 2019 Yunion
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package azure
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io"
    21  	"io/ioutil"
    22  	"net/http"
    23  	"os"
    24  	"strings"
    25  
    26  	"yunion.io/x/jsonutils"
    27  	"yunion.io/x/log"
    28  	"yunion.io/x/pkg/errors"
    29  
    30  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    31  	"yunion.io/x/onecloud/pkg/compute/options"
    32  	"yunion.io/x/onecloud/pkg/mcclient"
    33  	"yunion.io/x/onecloud/pkg/mcclient/auth"
    34  	modules "yunion.io/x/onecloud/pkg/mcclient/modules/image"
    35  	"yunion.io/x/cloudmux/pkg/multicloud"
    36  	"yunion.io/x/onecloud/pkg/util/fileutils2"
    37  	"yunion.io/x/onecloud/pkg/util/qemuimg"
    38  )
    39  
    40  const (
    41  	DefaultStorageAccount string = "image"
    42  	DefaultContainer      string = "image-cache"
    43  
    44  	DefaultReadBlockSize int64 = 4 * 1024 * 1024
    45  )
    46  
    47  type SStoragecache struct {
    48  	multicloud.SResourceBase
    49  	AzureTags
    50  	region *SRegion
    51  }
    52  
    53  func (self *SStoragecache) GetId() string {
    54  	return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Id, self.region.GetId())
    55  }
    56  
    57  func (self *SStoragecache) GetName() string {
    58  	return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Name, self.region.GetId())
    59  }
    60  
    61  func (self *SStoragecache) GetStatus() string {
    62  	return "available"
    63  }
    64  
    65  func (self *SStoragecache) Refresh() error {
    66  	return nil
    67  }
    68  
    69  func (self *SStoragecache) GetGlobalId() string {
    70  	return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Id, self.region.GetGlobalId())
    71  }
    72  
    73  func (self *SStoragecache) IsEmulated() bool {
    74  	return false
    75  }
    76  
    77  func (self *SStoragecache) GetICloudImages() ([]cloudprovider.ICloudImage, error) {
    78  	return nil, cloudprovider.ErrNotImplemented
    79  }
    80  
    81  func (self *SStoragecache) GetICustomizedCloudImages() ([]cloudprovider.ICloudImage, error) {
    82  	images, err := self.region.GetImages(cloudprovider.ImageTypeCustomized)
    83  	if err != nil {
    84  		return nil, errors.Wrapf(err, "GetImages")
    85  	}
    86  	ret := []cloudprovider.ICloudImage{}
    87  	for i := 0; i < len(images); i++ {
    88  		images[i].storageCache = self
    89  		ret = append(ret, &images[i])
    90  	}
    91  	return ret, nil
    92  }
    93  
    94  func (self *SStoragecache) GetIImageById(extId string) (cloudprovider.ICloudImage, error) {
    95  	img, err := self.region.GetImageById(extId)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	img.storageCache = self
   100  	return &img, nil
   101  }
   102  
   103  func (self *SStoragecache) GetPath() string {
   104  	return ""
   105  }
   106  
   107  func (self *SStoragecache) UploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, callback func(progress float32)) (string, error) {
   108  	err := os.MkdirAll(options.Options.TempPath, os.ModePerm)
   109  	if err != nil {
   110  		log.Warningf("failed to create tmp path %s error: %v", options.Options.TempPath, err)
   111  	}
   112  	return self.uploadImage(ctx, userCred, image, options.Options.TempPath, callback)
   113  }
   114  
   115  func (self *SStoragecache) checkStorageAccount() (*SStorageAccount, error) {
   116  	storageaccounts, err := self.region.ListStorageAccounts()
   117  	if err != nil {
   118  		return nil, errors.Wrap(err, "ListStorageAccounts")
   119  	}
   120  	if len(storageaccounts) == 0 {
   121  		storageaccount, err := self.region.CreateStorageAccount(self.region.Name)
   122  		if err != nil {
   123  			return nil, errors.Wrap(err, "CreateStorageAccount")
   124  		}
   125  		return storageaccount, nil
   126  	}
   127  	for i := 0; i < len(storageaccounts); i++ {
   128  		if id, ok := storageaccounts[i].Tags["id"]; ok && id == self.region.Name {
   129  			return &storageaccounts[i], nil
   130  		}
   131  	}
   132  
   133  	storageaccount := &storageaccounts[0]
   134  	if storageaccount.Tags == nil {
   135  		storageaccount.Tags = map[string]string{}
   136  	}
   137  	storageaccount.Tags["id"] = self.region.Name
   138  	err = self.region.update(jsonutils.Marshal(storageaccount), nil)
   139  	if err != nil {
   140  		return nil, errors.Wrapf(err, "Update(%s)", jsonutils.Marshal(storageaccount).String())
   141  	}
   142  	return storageaccount, nil
   143  }
   144  
   145  func (self *SStoragecache) uploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, tmpPath string, callback func(progress float32)) (string, error) {
   146  	s := auth.GetAdminSession(ctx, options.Options.Region)
   147  	meta, reader, sizeBytes, err := modules.Images.Download(s, image.ImageId, string(qemuimg.VHD), false)
   148  	if err != nil {
   149  		return "", err
   150  	}
   151  	// {
   152  	// 	"checksum":"d0ab0450979977c6ada8d85066a6e484",
   153  	// 	"container_format":"bare",
   154  	// 	"created_at":"2018-08-10T04:18:07",
   155  	// 	"deleted":"False",
   156  	// 	"disk_format":"vhd",
   157  	// 	"id":"64189033-3ad4-413c-b074-6bf0b6be8508",
   158  	// 	"is_public":"False",
   159  	// 	"min_disk":"0",
   160  	// 	"min_ram":"0",
   161  	// 	"name":"centos-7.3.1611-20180104.vhd",
   162  	// 	"owner":"5124d80475434da8b41fee48d5be94df",
   163  	// 	"properties":{
   164  	// 		"os_arch":"x86_64",
   165  	// 		"os_distribution":"CentOS",
   166  	// 		"os_type":"Linux",
   167  	// 		"os_version":"7.3.1611-VHD"
   168  	// 	},
   169  	// 	"protected":"False",
   170  	// 	"size":"2028505088",
   171  	// 	"status":"active",
   172  	// 	"updated_at":"2018-08-10T04:20:59"
   173  	// }
   174  	log.Infof("meta data %s", meta)
   175  
   176  	imageNameOnBlob, _ := meta.GetString("name")
   177  	if !strings.HasSuffix(imageNameOnBlob, ".vhd") {
   178  		imageNameOnBlob = fmt.Sprintf("%s.vhd", imageNameOnBlob)
   179  	}
   180  	tmpFile := fmt.Sprintf("%s/%s", tmpPath, imageNameOnBlob)
   181  	defer os.Remove(tmpFile)
   182  	f, err := os.Create(tmpFile)
   183  	if err != nil {
   184  		return "", errors.Wrap(err, "os.Create(tmpFile)")
   185  	}
   186  	defer f.Close()
   187  
   188  	// 下载占33%
   189  	r := multicloud.NewProgress(sizeBytes, 33, reader, callback)
   190  	if _, err := io.Copy(f, r); err != nil {
   191  		return "", errors.Wrap(err, "io.Copy(f, reader)")
   192  	}
   193  
   194  	storageaccount, err := self.checkStorageAccount()
   195  	if err != nil {
   196  		return "", errors.Wrap(err, "self.checkStorageAccount")
   197  	}
   198  
   199  	blobURI, err := storageaccount.UploadFile("image-cache", tmpFile, callback)
   200  	if err != nil {
   201  		return "", errors.Wrap(err, "storageaccount.UploadFile")
   202  	}
   203  
   204  	// size, _ := meta.Int("size")
   205  
   206  	img, err := self.region.CreateImageByBlob(image.ImageId, image.OsType, blobURI, int32(sizeBytes>>30))
   207  	if err != nil {
   208  		return "", errors.Wrap(err, "CreateImageByBlob")
   209  	}
   210  	if callback != nil {
   211  		callback(100)
   212  	}
   213  	return img.GetGlobalId(), nil
   214  }
   215  
   216  func (self *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) {
   217  	if image, err := self.region.CreateImage(snapshotId, imageName, osType, imageDesc); err != nil {
   218  		return nil, err
   219  	} else {
   220  		image.storageCache = self
   221  		return image, nil
   222  	}
   223  }
   224  
   225  func (self *SStoragecache) DownloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) {
   226  	return self.downloadImage(userCred, imageId, extId, path)
   227  }
   228  
   229  func (self *SStoragecache) downloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) {
   230  	// TODO: need to fix scenarios where image is a public image
   231  	// XXX Qiu Jian
   232  	if image, err := self.region.getPrivateImage(extId); err != nil {
   233  		return nil, err
   234  	} else if snapshotId := image.Properties.StorageProfile.OsDisk.Snapshot.ID; len(snapshotId) == 0 {
   235  		return nil, cloudprovider.ErrNotFound
   236  	} else if uri, err := self.region.GrantAccessSnapshot(snapshotId); err != nil {
   237  		return nil, err
   238  	} else if resp, err := http.Get(uri); err != nil {
   239  		return nil, err
   240  	} else {
   241  		tmpImageFile, err := ioutil.TempFile(path, "temp")
   242  		if err != nil {
   243  			return nil, err
   244  		}
   245  		defer tmpImageFile.Close()
   246  		defer os.Remove(tmpImageFile.Name())
   247  		{
   248  			sf := fileutils2.NewSparseFileWriter(tmpImageFile)
   249  			data := make([]byte, DefaultReadBlockSize)
   250  			written := int64(0)
   251  			for {
   252  				n, err := resp.Body.Read(data)
   253  				if n > 0 {
   254  					if n, err := sf.Write(data); err != nil {
   255  						return nil, err
   256  					} else {
   257  						written += int64(n)
   258  					}
   259  				} else if err == io.EOF {
   260  					if written <= resp.ContentLength {
   261  						return nil, fmt.Errorf("got eof: expecting %d bytes, got %d", resp.ContentLength, written)
   262  					}
   263  					break
   264  				} else {
   265  					return nil, err
   266  				}
   267  			}
   268  			if err := sf.PreClose(); err != nil {
   269  				return nil, err
   270  			}
   271  		}
   272  
   273  		if _, err := tmpImageFile.Seek(0, os.SEEK_SET); err != nil {
   274  			return nil, errors.Wrap(err, "seek")
   275  		}
   276  		s := auth.GetAdminSession(context.Background(), options.Options.Region)
   277  		params := jsonutils.Marshal(map[string]string{"image_id": imageId, "disk-format": "raw"})
   278  		if result, err := modules.Images.Upload(s, params, tmpImageFile, resp.ContentLength); err != nil {
   279  			return nil, err
   280  		} else {
   281  			return result, nil
   282  		}
   283  
   284  	}
   285  }
   286  
   287  func (region *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) {
   288  	storageCache := region.getStoragecache()
   289  	return []cloudprovider.ICloudStoragecache{storageCache}, nil
   290  }