| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977 |
- // 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 qemuimg
- import (
- "fmt"
- "os"
- "runtime"
- "strconv"
- "strings"
- "yunion.io/x/cloudmux/pkg/cloudprovider"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/util/qemuimgfmt"
- "yunion.io/x/pkg/utils"
- api "yunion.io/x/onecloud/pkg/apis/compute"
- imageapi "yunion.io/x/onecloud/pkg/apis/image"
- "yunion.io/x/onecloud/pkg/util/fileutils2"
- "yunion.io/x/onecloud/pkg/util/procutils"
- "yunion.io/x/onecloud/pkg/util/qemutils"
- "yunion.io/x/onecloud/pkg/util/seclib2"
- )
- var (
- ErrUnsupportedFormat = errors.Error("unsupported format")
- convertWorkInOrder = false
- convertCoroutines = 16
- )
- func SetConvertWorkInOrder(workInOrder bool) {
- convertWorkInOrder = workInOrder
- }
- func SetConvertCoroutines(coroutines int) error {
- if coroutines < 1 || coroutines > 16 {
- return errors.Errorf("coroutines %d out of range 1-16", coroutines)
- }
- convertCoroutines = coroutines
- return nil
- }
- type TIONiceLevel int
- const (
- // The scheduling class. 0 for none, 1 for real time, 2 for best-effort, 3 for idle.
- IONiceNone = TIONiceLevel(0)
- IONiceRealTime = TIONiceLevel(1)
- IONiceBestEffort = TIONiceLevel(2)
- IONiceIdle = TIONiceLevel(3)
- )
- const DefaultConvertCorutines = 8
- const DefaultQcow2ClusterSize = 65536
- var preallocation = "metadata"
- func SetPreallocation(prealloc string) error {
- if !utils.IsInStringArray(prealloc, []string{"", "disable", "metadata", "falloc", "full"}) {
- return errors.Errorf("unsupported preallocation %s", prealloc)
- }
- if prealloc == "disable" {
- prealloc = ""
- }
- preallocation = prealloc
- return nil
- }
- type SQemuImage struct {
- Path string
- Password string
- Format qemuimgfmt.TImageFormat
- SizeBytes int64
- ActualSizeBytes int64
- ClusterSize int
- BackFilePath string
- Compat string
- Encrypted bool
- Subformat string
- IoLevel TIONiceLevel
- EncryptFormat TEncryptFormat
- EncryptAlg seclib2.TSymEncAlg
- }
- func NewQemuImage(path string) (*SQemuImage, error) {
- return NewQemuImageWithIOLevel(path, IONiceNone)
- }
- func NewQemuImageWithIOLevel(path string, ioLevel TIONiceLevel) (*SQemuImage, error) {
- qemuImg := SQemuImage{Path: path, IoLevel: ioLevel}
- err := qemuImg.parse()
- if err != nil {
- return nil, err
- }
- return &qemuImg, nil
- }
- func (img *SQemuImage) parse() error {
- if len(img.Path) == 0 {
- return fmt.Errorf("empty image path")
- }
- if strings.HasPrefix(img.Path, "nbd") {
- // nbd TCP -> nbd:<server-ip>:<port>
- // nbd Unix Domain Sockets -> nbd:unix:<domain-socket-file>
- img.ActualSizeBytes = 0
- } else if strings.HasPrefix(img.Path, "iscsi") {
- // iSCSI LUN -> iscsi://<target-ip>[:<port>]/<target-iqn>/<lun>
- return cloudprovider.ErrNotImplemented
- } else if strings.HasPrefix(img.Path, "sheepdog") {
- // sheepdog -> sheepdog[+tcp|+unix]://[host:port]/vdiname[?socket=path][#snapid|#tag]
- return cloudprovider.ErrNotImplemented
- } else if strings.HasPrefix(img.Path, api.STORAGE_RBD) {
- img.ActualSizeBytes = 0
- } else {
- // check file existence
- fileInfo, err := procutils.RemoteStat(img.Path)
- if err != nil {
- if !os.IsNotExist(err) {
- return errors.Wrapf(err, "remote stat of %s", img.Path)
- } else {
- // not created yet
- return nil
- }
- } else {
- img.ActualSizeBytes = fileInfo.Size()
- }
- }
- resp, err := func() (jsonutils.JSONObject, error) {
- output, err := procutils.NewRemoteCommandAsFarAsPossible(qemutils.GetQemuImg(), "info", "-U", img.Path, "--output", "json").Output()
- if err != nil {
- return nil, errors.Wrapf(err, "qemu-img info: %s", output)
- }
- return jsonutils.Parse(output)
- }()
- if err != nil {
- return err
- }
- /*
- {
- "virtual-size": 107374182400,
- "filename": "/opt/cloud/workspace/data/glance/images/3ccecd2b-0ab4-4525-8e64-6f1d3c3a2457",
- "cluster-size": 65536,
- "format": "vmdk",
- "actual-size": 2173186048,
- "format-specific": {
- "type": "vmdk",
- "data": {
- "cid": 3046516340,
- "parent-cid": 4294967295,
- "create-type": "streamOptimized",
- "extents": [
- {
- "compressed": true,
- "virtual-size": 107374182400,
- "filename": "/opt/cloud/workspace/data/glance/images/3ccecd2b-0ab4-4525-8e64-6f1d3c3a2457",
- "cluster-size": 65536,
- "format": ""
- }
- ]
- }
- },
- "dirty-flag": false
- }
- */
- info := struct {
- VirtualSizeBytes int64 `json:"virtual-size"`
- Filename string `json:"filename"`
- Format string `json:"format"`
- ActualSizeBytes int64 `json:"actual-size"`
- ClusterSize int `json:"cluster-size"`
- BackingFilename string `json:"backing-filename"`
- FullBackingFilename string `json:"full-backing-filename"`
- BackingFilenameFormat string `json:"backing-filename-format"`
- Encrypted bool `json:"encrypted"`
- FormatSpecific struct {
- Type string `json:"type"`
- Data struct {
- Cid uint64 `json:"cid"`
- ParentCid uint64 `json:"parent-cid"`
- CreateType string `json:"create-type"`
- Extents []struct {
- Compressed bool `json:"compressed"`
- VirtualSize uint64 `json:"virtual-size"`
- Filename string `json:"filename"`
- ClusterSize uint64 `json:"cluster-size"`
- Format string `json:"format"`
- } `json:"extents"`
- Compat string `json:"compat"`
- LazyRefcounts int `json:"lazy-refcounts"`
- RefcountBits int `json:"refcount-bits"`
- Corrupt bool `json:"corrupt"`
- Encrypt struct {
- IvgenAlg string `json:"ivgen-alg"`
- HashAlg string `json:"hash-alg"`
- CipherAlg string `json:"cipher-alg"`
- Uuid string `json:"uuid"`
- Format string `json:"format"`
- CipherMod string `json:"cipher-mode"`
- } `json:"encrypt"`
- } `json:"data"`
- } `json:"format-specific"`
- CreateType string `json:"create-type"`
- DirtyFlag bool `json:"dirty-flag"`
- }{}
- err = resp.Unmarshal(&info)
- if err != nil {
- return errors.Wrapf(err, "resp.Unmarshal")
- }
- img.Format = qemuimgfmt.TImageFormat(info.Format)
- img.SizeBytes = info.VirtualSizeBytes
- img.ClusterSize = info.ClusterSize
- img.Compat = info.FormatSpecific.Data.Compat
- img.Encrypted = info.Encrypted
- img.BackFilePath, err = ParseQemuFilepath(info.FullBackingFilename)
- if err != nil {
- return errors.Wrap(err, "ParseQemuFilepath")
- }
- img.Subformat = info.CreateType
- if img.Subformat == "" {
- img.Subformat = info.FormatSpecific.Data.CreateType
- }
- if img.Encrypted {
- img.EncryptFormat = TEncryptFormat(info.FormatSpecific.Data.Encrypt.Format)
- img.EncryptAlg = seclib2.TSymEncAlg(info.FormatSpecific.Data.Encrypt.CipherAlg)
- }
- // test if it is an ISO
- if img.Format == qemuimgfmt.RAW && fileutils2.IsFile(img.Path) && fileutils2.IsIsoFile(img.Path) {
- img.Format = qemuimgfmt.ISO
- }
- if img.Format == qemuimgfmt.RAW && fileutils2.IsFile(img.Path) && (fileutils2.IsTarGzipFile(img.Path) || fileutils2.IsTarFile(img.Path)) {
- img.Format = imageapi.IMAGE_DISK_FORMAT_TGZ
- }
- return nil
- }
- // base64 password
- func (img *SQemuImage) SetPassword(password string) {
- img.Password = password
- }
- func (img *SQemuImage) IsValid() bool {
- return len(img.Format) > 0
- }
- func (img *SQemuImage) IsChained() bool {
- return len(img.BackFilePath) > 0
- }
- func (img *SQemuImage) GetBackingChain() ([]string, error) {
- if len(img.BackFilePath) > 0 {
- backImg, err := NewQemuImage(img.BackFilePath)
- if err != nil {
- return nil, err
- }
- backingChain, err := backImg.GetBackingChain()
- if err != nil {
- return nil, err
- }
- return append(backingChain, img.BackFilePath), nil
- } else {
- return []string{}, nil
- }
- }
- type TEncryptFormat string
- const (
- EncryptFormatLuks = "luks"
- )
- type SImageInfo struct {
- Path string
- Format qemuimgfmt.TImageFormat
- IoLevel TIONiceLevel
- Password string
- ClusterSize int
- // only luks supported
- EncryptFormat TEncryptFormat
- // aes-256, sm4
- EncryptAlg seclib2.TSymEncAlg
- secId string
- }
- func (info *SImageInfo) SetSecId(id string) {
- info.secId = id
- }
- func (info SImageInfo) ImageOptions() string {
- opts := make([]string, 0)
- format := info.Format
- if len(format) == 0 {
- format = qemuimgfmt.QCOW2
- }
- opts = append(opts, fmt.Sprintf("driver=%s", format))
- opts = append(opts, fmt.Sprintf("file.filename=%s", info.Path))
- if info.Encrypted() {
- encFormat := info.EncryptFormat
- if len(encFormat) == 0 {
- encFormat = EncryptFormatLuks
- }
- opts = append(opts, fmt.Sprintf("encrypt.format=%s", encFormat))
- secId := info.secId
- if len(secId) == 0 {
- secId = "sec0"
- }
- opts = append(opts, fmt.Sprintf("encrypt.key-secret=%s", secId))
- // if info.EncryptFormat == EncryptFormatLuks {
- // opts = append(opts, fmt.Sprintf("encrypt.cipher-alg=%s", info.EncryptAlg))
- // }
- }
- return strings.Join(opts, ",")
- }
- func (info SImageInfo) SecretOptions() string {
- if info.Encrypted() {
- opts := make([]string, 0)
- secId := info.secId
- if len(secId) == 0 {
- secId = "sec0"
- }
- opts = append(opts, "secret")
- opts = append(opts, fmt.Sprintf("id=%s", secId))
- opts = append(opts, fmt.Sprintf("data=%s", info.Password))
- opts = append(opts, "format=base64")
- return strings.Join(opts, ",")
- }
- return ""
- }
- func (info SImageInfo) Encrypted() bool {
- return len(info.Password) > 0
- }
- func Convert(srcInfo, destInfo SImageInfo, compact bool, workerOpions []string) error {
- if srcInfo.Encrypted() || destInfo.Encrypted() {
- return convertEncrypt(srcInfo, destInfo, compact, workerOpions)
- } else {
- return convertOther(srcInfo, destInfo, compact, workerOpions)
- }
- }
- func convertOther(srcInfo, destInfo SImageInfo, compact bool, workerOpions []string) error {
- cmdline := []string{"-c", strconv.Itoa(int(srcInfo.IoLevel)),
- qemutils.GetQemuImg(), "convert"}
- if compact {
- cmdline = append(cmdline, "-c")
- }
- if workerOpions == nil {
- // https://bugzilla.redhat.com/show_bug.cgi?id=1969848
- // https://bugs.launchpad.net/qemu/+bug/1805256
- // qemu-img convert may hang on aarch64, fix: add -m 1
- // no need to limit 1 any more, the bug has been fixed! - QIUJIAN
- // cmdline = append(cmdline, "-m", "1")
- } else {
- cmdline = append(cmdline, workerOpions...)
- }
- if !utils.IsInStringArray("-W", workerOpions) && !convertWorkInOrder {
- cmdline = append(cmdline, "-W")
- }
- if !utils.IsInStringArray("-m", workerOpions) && convertCoroutines != DefaultConvertCorutines {
- cmdline = append(cmdline, "-m", strconv.Itoa(convertCoroutines))
- }
- if compact {
- cmdline = append(cmdline, "-c")
- if destInfo.ClusterSize <= 0 {
- destInfo.ClusterSize = DefaultQcow2ClusterSize
- }
- }
- cmdline = append(cmdline, "-f", srcInfo.Format.String(), "-O", destInfo.Format.String())
- options := []string{}
- if destInfo.Format.String() == "vmdk" { // for esxi vmdk
- options = append(options, vmdkOptions(compact)...)
- }
- if destInfo.Format == qemuimgfmt.QCOW2 {
- if destInfo.ClusterSize > 0 {
- options = append(options, fmt.Sprintf("cluster_size=%d", destInfo.ClusterSize))
- } else if srcInfo.ClusterSize > 0 {
- options = append(options, fmt.Sprintf("cluster_size=%d", srcInfo.ClusterSize))
- }
- }
- if len(options) > 0 {
- cmdline = append(cmdline, "-o", strings.Join(options, ","))
- }
- cmdline = append(cmdline, srcInfo.Path, destInfo.Path)
- log.Infof("XXXX qemu-img command: %s", cmdline)
- cmd := procutils.NewRemoteCommandAsFarAsPossible("ionice", cmdline...)
- if runtime.GOOS == "darwin" {
- cmdline = cmdline[2:]
- cmd = procutils.NewRemoteCommandAsFarAsPossible(cmdline[0], cmdline[1:]...)
- }
- output, err := cmd.Output()
- if err != nil {
- os.Remove(destInfo.Path)
- return errors.Wrapf(err, "convert: %s", output)
- }
- return nil
- }
- func convertEncrypt(srcInfo, destInfo SImageInfo, compact bool, workerOpions []string) error {
- source, err := NewQemuImageWithIOLevel(srcInfo.Path, srcInfo.IoLevel)
- if err != nil {
- return errors.Wrapf(err, "NewQemuImage source %s", srcInfo.Path)
- }
- target, err := NewQemuImage(destInfo.Path)
- if err != nil {
- return errors.Wrapf(err, "NewQemuImage dest %s", destInfo.Path)
- }
- if target.Format != qemuimgfmt.QCOW2 {
- err = target.CreateQcow2(source.GetSizeMB(), compact, "", destInfo.Password, destInfo.EncryptFormat, destInfo.EncryptAlg)
- if err != nil {
- return errors.Wrapf(err, "Create target image %s", destInfo.Path)
- }
- }
- cmdline := []string{"-c", strconv.Itoa(int(srcInfo.IoLevel)), qemutils.GetQemuImg(), "convert"}
- if compact {
- cmdline = append(cmdline, "-c")
- }
- if workerOpions == nil {
- // https://bugzilla.redhat.com/show_bug.cgi?id=1969848
- // https://bugs.launchpad.net/qemu/+bug/1805256
- // qemu-img convert may hang on aarch64, fix: add -m 1
- // no need to limit 1 any more, the bug has been fixed! - QIUJIAN
- // cmdline = append(cmdline, "-m", "1")
- } else {
- cmdline = append(cmdline, workerOpions...)
- }
- if !utils.IsInStringArray("-W", workerOpions) && !convertWorkInOrder {
- cmdline = append(cmdline, "-W")
- }
- if !utils.IsInStringArray("-m", workerOpions) && convertCoroutines != DefaultConvertCorutines {
- cmdline = append(cmdline, "-m", strconv.Itoa(convertCoroutines))
- }
- if srcInfo.Encrypted() {
- if srcInfo.Format != qemuimgfmt.QCOW2 {
- return errors.Wrap(errors.ErrNotSupported, "source image not support encryption")
- }
- }
- if destInfo.Encrypted() {
- if destInfo.Format != qemuimgfmt.QCOW2 {
- return errors.Wrap(errors.ErrNotSupported, "target image not support encryption")
- }
- }
- if srcInfo.Encrypted() && destInfo.Encrypted() {
- if srcInfo.Password == destInfo.Password {
- srcInfo.secId = "sec0"
- destInfo.secId = "sec0"
- cmdline = append(cmdline, "--object", srcInfo.SecretOptions())
- } else {
- srcInfo.secId = "sec0"
- cmdline = append(cmdline, "--object", srcInfo.SecretOptions())
- destInfo.secId = "sec1"
- cmdline = append(cmdline, "--object", destInfo.SecretOptions())
- }
- } else if srcInfo.Encrypted() {
- srcInfo.secId = "sec0"
- cmdline = append(cmdline, "--object", srcInfo.SecretOptions())
- } else if destInfo.Encrypted() {
- destInfo.secId = "sec0"
- cmdline = append(cmdline, "--object", destInfo.SecretOptions())
- } else {
- // dead branch
- }
- cmdline = append(cmdline, "--image-opts", srcInfo.ImageOptions())
- cmdline = append(cmdline, "--target-image-opts", destInfo.ImageOptions())
- cmdline = append(cmdline, "-n")
- cmd := procutils.NewRemoteCommandAsFarAsPossible("ionice", cmdline...)
- if runtime.GOOS == "darwin" {
- cmdline = cmdline[2:]
- cmd = procutils.NewRemoteCommandAsFarAsPossible(cmdline[0], cmdline[1:]...)
- }
- output, err := cmd.Output()
- log.Infof("XXXX qemu-img convert command: %s output: %s", cmdline, output)
- if err != nil {
- os.Remove(destInfo.Path)
- return errors.Wrapf(err, "convert: %s", string(output))
- }
- return nil
- }
- func (img *SQemuImage) doConvert(targetPath string, format qemuimgfmt.TImageFormat, compact bool, password string, encryptFormat TEncryptFormat, encryptAlg seclib2.TSymEncAlg) error {
- if !img.IsValid() {
- return fmt.Errorf("self is not valid")
- }
- destClusterSize := img.ClusterSize
- if compact {
- destClusterSize = DefaultQcow2ClusterSize
- }
- return Convert(SImageInfo{
- Path: img.Path,
- Format: img.Format,
- IoLevel: img.IoLevel,
- Password: img.Password,
- EncryptFormat: img.EncryptFormat,
- EncryptAlg: img.EncryptAlg,
- ClusterSize: img.ClusterSize,
- }, SImageInfo{
- Path: targetPath,
- Format: format,
- Password: password,
- EncryptFormat: encryptFormat,
- EncryptAlg: encryptAlg,
- ClusterSize: destClusterSize,
- }, compact, nil)
- }
- func (img *SQemuImage) Clone(name string, format qemuimgfmt.TImageFormat, compact bool) (*SQemuImage, error) {
- switch format {
- case qemuimgfmt.QCOW2:
- return img.CloneQcow2(name, compact)
- case qemuimgfmt.VMDK:
- return img.CloneVmdk(name, compact)
- case qemuimgfmt.RAW:
- return img.CloneRaw(name)
- case qemuimgfmt.VHD:
- return img.CloneVhd(name)
- default:
- return nil, ErrUnsupportedFormat
- }
- }
- func (img *SQemuImage) clone(target string, format qemuimgfmt.TImageFormat, compact bool, password string, encryptFormat TEncryptFormat, encryptAlg seclib2.TSymEncAlg) (*SQemuImage, error) {
- err := img.doConvert(target, format, compact, password, encryptFormat, encryptAlg)
- if err != nil {
- return nil, errors.Wrap(err, "doConvert")
- }
- return NewQemuImage(target)
- }
- func (img *SQemuImage) convert(format qemuimgfmt.TImageFormat, compact bool, password string, encryptFormat TEncryptFormat, alg seclib2.TSymEncAlg) error {
- tmpPath := fmt.Sprintf("%s.%s", img.Path, utils.GenRequestId(36))
- err := img.doConvert(tmpPath, format, compact, password, encryptFormat, alg)
- if err != nil {
- return errors.Wrap(err, "doConvert")
- }
- cmd := procutils.NewRemoteCommandAsFarAsPossible("mv", "-f", tmpPath, img.Path)
- output, err := cmd.Output()
- if err != nil {
- os.Remove(tmpPath)
- return errors.Wrapf(err, "move %s", string(output))
- }
- img.Password = password
- img.EncryptFormat = encryptFormat
- img.EncryptAlg = alg
- return img.parse()
- }
- func (img *SQemuImage) convertTo(
- format qemuimgfmt.TImageFormat, compact bool, password string, output string, encFormat TEncryptFormat, encAlg seclib2.TSymEncAlg,
- ) error {
- err := img.doConvert(output, format, compact, password, encFormat, encAlg)
- if err != nil {
- return errors.Wrap(err, "doConvert")
- }
- img.Password = password
- return img.parse()
- }
- func (img *SQemuImage) Copy(name string) (*SQemuImage, error) {
- if !img.IsValid() {
- return nil, fmt.Errorf("self is not valid")
- }
- cmd := procutils.NewRemoteCommandAsFarAsPossible("cp", "--sparse=always", img.Path, name)
- output, err := cmd.Output()
- if err != nil {
- os.Remove(name)
- return nil, errors.Wrapf(err, "cp: %s", string(output))
- }
- newImg, err := NewQemuImage(name)
- if err != nil {
- return nil, errors.Wrap(err, "NewQemuImage")
- }
- newImg.Password = img.Password
- return newImg, nil
- }
- func (img *SQemuImage) Convert2Qcow2To(output string, compact bool, password string, encFormat TEncryptFormat, encAlg seclib2.TSymEncAlg) error {
- return img.convertTo(qemuimgfmt.QCOW2, compact, password, output, encFormat, encAlg)
- }
- func (img *SQemuImage) Convert2Qcow2(compact bool, password string, encFormat TEncryptFormat, encAlg seclib2.TSymEncAlg) error {
- if len(password) == 0 && len(img.Password) > 0 {
- password = img.Password
- encFormat = img.EncryptFormat
- encAlg = img.EncryptAlg
- }
- return img.convert(qemuimgfmt.QCOW2, compact, password, encFormat, encAlg)
- }
- func (img *SQemuImage) Convert2Vmdk(compact bool) error {
- return img.convert(qemuimgfmt.VMDK, compact, "", "", "")
- }
- func (img *SQemuImage) Convert2Vhd() error {
- return img.convert(qemuimgfmt.VHD, false, "", "", "")
- }
- func (img *SQemuImage) Convert2Raw() error {
- return img.convert(qemuimgfmt.RAW, false, "", "", "")
- }
- func (img *SQemuImage) IsRaw() bool {
- return img.Format == qemuimgfmt.RAW
- }
- func (img *SQemuImage) IsSparseQcow2() bool {
- return img.Format == qemuimgfmt.QCOW2 && img.ClusterSize >= 1024*1024*2
- }
- func (img *SQemuImage) IsSparseVmdk() bool {
- return img.Format == qemuimgfmt.VMDK && img.Subformat != "streamOptimized"
- }
- func (img *SQemuImage) IsSparse() bool {
- return img.IsRaw() || img.IsSparseQcow2() || img.IsSparseVmdk()
- }
- func (img *SQemuImage) Expand() error {
- if img.IsSparse() {
- return nil
- }
- return img.Convert2Qcow2(false, img.Password, img.EncryptFormat, img.EncryptAlg)
- }
- func (img *SQemuImage) CloneQcow2(name string, compact bool) (*SQemuImage, error) {
- return img.clone(name, qemuimgfmt.QCOW2, compact, img.Password, img.EncryptFormat, img.EncryptAlg)
- }
- func vmdkOptions(compact bool) []string {
- if compact {
- return []string{"subformat=streamOptimized"}
- } else {
- return []string{"subformat=monolithicSparse"}
- }
- }
- // func vhdOptions(compact bool) []string {
- // if compact {
- // return []string{"subformat=dynamic"}
- // } else {
- // return []string{"subformat=fixed"}
- // }
- // }
- func (img *SQemuImage) CloneVmdk(name string, compact bool) (*SQemuImage, error) {
- return img.clone(name, qemuimgfmt.VMDK, compact, "", "", "")
- }
- func (img *SQemuImage) CloneVhd(name string) (*SQemuImage, error) {
- return img.clone(name, qemuimgfmt.VHD, false, "", "", "")
- }
- func (img *SQemuImage) CloneRaw(name string) (*SQemuImage, error) {
- return img.clone(name, qemuimgfmt.RAW, false, "", "", "")
- }
- func (img *SQemuImage) create(sizeMB int, format qemuimgfmt.TImageFormat, options []string, extraArgs []string) error {
- if img.IsValid() && img.Format != qemuimgfmt.RAW {
- return fmt.Errorf("create: the image is valid??? %s", img.Format)
- }
- args := []string{"-c", strconv.Itoa(int(img.IoLevel)),
- qemutils.GetQemuImg(), "create", "-f", format.String()}
- if len(options) > 0 {
- args = append(args, "-o", strings.Join(options, ","))
- }
- if len(extraArgs) > 0 {
- args = append(args, extraArgs...)
- }
- args = append(args, img.Path)
- if sizeMB > 0 {
- args = append(args, fmt.Sprintf("%dM", sizeMB))
- }
- cmd := procutils.NewRemoteCommandAsFarAsPossible("ionice", args...)
- if runtime.GOOS == "darwin" {
- args = args[2:]
- cmd = procutils.NewRemoteCommandAsFarAsPossible(args[0], args[1:]...)
- }
- output, err := cmd.Output()
- if err != nil {
- return errors.Wrapf(err, "create image failed: %s", output)
- }
- return img.parse()
- }
- type SFileInfo struct {
- Driver string `json:"driver"`
- Filename string `json:"filename"`
- Locking string `json:"locking"`
- }
- type SQcow2FileInfo struct {
- Driver string `json:"driver"`
- EncryptKeySecret string `json:"encrypt.key-secret"`
- EncryptFormat string `json:"encrypt.format"`
- File SFileInfo `json:"file"`
- }
- func newQcow2FileInfo(filePath string) SQcow2FileInfo {
- return SQcow2FileInfo{
- Driver: "qcow2",
- File: SFileInfo{
- Driver: "file",
- Filename: filePath,
- Locking: "off",
- },
- }
- }
- func GetQemuFilepath(path string, encKey string, encFormat TEncryptFormat) string {
- if len(encKey) == 0 {
- return path
- }
- info := newQcow2FileInfo(path)
- info.EncryptKeySecret = encKey
- info.EncryptFormat = string(encFormat)
- return fmt.Sprintf("json:%s", jsonutils.Marshal(info))
- }
- func (img *SQemuImage) CreateQcow2(sizeMB int, compact bool, backPath string, password string, encFormat TEncryptFormat, encAlg seclib2.TSymEncAlg) error {
- options := make([]string, 0)
- extraArgs := make([]string, 0)
- if len(password) > 0 {
- extraArgs = append(extraArgs, "--object", fmt.Sprintf("secret,id=sec0,data=%s,format=base64", password))
- }
- if len(backPath) > 0 {
- // options = append(options, fmt.Sprintf("backing_file=%s", backPath))
- backQemu, err := NewQemuImage(backPath)
- if err != nil {
- return errors.Wrap(err, "parse backing file")
- }
- if backQemu.Encrypted {
- extraArgs = append(extraArgs, "-b", GetQemuFilepath(backPath, "sec0", EncryptFormatLuks))
- } else {
- extraArgs = append(extraArgs, "-b", backPath)
- }
- if len(string(backQemu.Format)) > 0 {
- extraArgs = append(extraArgs, "-F", string(backQemu.Format))
- }
- if !compact {
- options = append(options, "cluster_size=2M")
- }
- if sizeMB == 0 {
- sizeMB = backQemu.GetSizeMB()
- }
- } else if !compact {
- sparseOpts := qcow2SparseOptions()
- if preallocation != "" {
- if sizeMB <= 1024*1024*4 {
- options = append(options, fmt.Sprintf("preallocation=%s", preallocation))
- }
- }
- options = append(options, sparseOpts...)
- }
- if len(password) > 0 {
- if len(encFormat) == 0 {
- encFormat = EncryptFormatLuks
- }
- options = append(options, fmt.Sprintf("encrypt.format=%s", encFormat))
- options = append(options, "encrypt.key-secret=sec0")
- if encFormat == EncryptFormatLuks {
- options = append(options, fmt.Sprintf("encrypt.cipher-alg=%s", encAlg))
- }
- }
- return img.create(sizeMB, qemuimgfmt.QCOW2, options, extraArgs)
- }
- func (img *SQemuImage) CreateVmdk(sizeMB int, compact bool) error {
- return img.create(sizeMB, qemuimgfmt.VMDK, vmdkOptions(compact), nil)
- }
- func (img *SQemuImage) CreateVhd(sizeMB int) error {
- return img.create(sizeMB, qemuimgfmt.VHD, nil, nil)
- }
- func (img *SQemuImage) CreateRaw(sizeMB int) error {
- return img.create(sizeMB, qemuimgfmt.RAW, nil, nil)
- }
- func (img *SQemuImage) GetSizeMB() int {
- return int(img.SizeBytes / 1024 / 1024)
- }
- func (img *SQemuImage) GetActualSizeMB() int {
- return int(img.ActualSizeBytes / 1024 / 1024)
- }
- func (img *SQemuImage) Resize(sizeMB int) error {
- if !img.IsValid() {
- return fmt.Errorf("self is not valid")
- }
- encInfo := SImageInfo{
- Path: img.Path,
- Format: img.Format,
- IoLevel: img.IoLevel,
- }
- args := make([]string, 0)
- args = append(args, "-c", strconv.Itoa(int(img.IoLevel)), qemutils.GetQemuImg(), "resize")
- if len(img.Password) > 0 {
- encInfo.Password = img.Password
- encInfo.EncryptFormat = img.EncryptFormat
- encInfo.EncryptAlg = img.EncryptAlg
- encInfo.secId = "sec0"
- args = append(args, "--object", encInfo.SecretOptions())
- }
- args = append(args, "--image-opts", encInfo.ImageOptions())
- args = append(args, fmt.Sprintf("%dM", sizeMB))
- cmd := procutils.NewRemoteCommandAsFarAsPossible("ionice", args...)
- if runtime.GOOS == "darwin" {
- args = args[2:]
- cmd = procutils.NewRemoteCommandAsFarAsPossible(args[0], args[1:]...)
- }
- output, err := cmd.Output()
- if err != nil {
- return errors.Wrapf(err, "resize: %s", string(output))
- }
- return img.parse()
- }
- func (img *SQemuImage) Rebase(backPath string, force bool) error {
- if !img.IsValid() {
- return fmt.Errorf("self is not valid")
- }
- encInfo := SImageInfo{
- Path: img.Path,
- Format: img.Format,
- IoLevel: img.IoLevel,
- }
- args := []string{"-c", strconv.Itoa(int(img.IoLevel)),
- qemutils.GetQemuImg(), "rebase"}
- if len(img.Password) > 0 {
- encInfo.Password = img.Password
- encInfo.EncryptFormat = img.EncryptFormat
- encInfo.EncryptAlg = img.EncryptAlg
- encInfo.secId = "sec0"
- args = append(args, "--object", encInfo.SecretOptions())
- }
- args = append(args, "--image-opts", encInfo.ImageOptions())
- if force {
- args = append(args, "-u")
- }
- args = append(args, "-b", backPath)
- cmd := procutils.NewRemoteCommandAsFarAsPossible("ionice", args...)
- if runtime.GOOS == "darwin" {
- args = args[2:]
- cmd = procutils.NewRemoteCommandAsFarAsPossible(args[0], args[1:]...)
- }
- output, err := cmd.Output()
- if err != nil {
- return errors.Wrapf(err, "rebase %s", string(output))
- }
- return img.parse()
- }
- func (img *SQemuImage) Delete() error {
- if !img.IsValid() {
- return nil
- }
- err := os.Remove(img.Path)
- if err != nil {
- log.Errorf("delete fail %s", err)
- return err
- }
- img.Format = ""
- img.ActualSizeBytes = 0
- img.SizeBytes = 0
- return nil
- }
- func (img *SQemuImage) Fallocate() error {
- if !img.IsValid() {
- return fmt.Errorf("self is not valid")
- }
- cmd := procutils.NewCommand("fallocate", "-l", fmt.Sprintf("%dm", img.GetSizeMB()), img.Path)
- output, err := cmd.Output()
- if err != nil {
- return errors.Wrapf(err, "fallocate: %s", string(output))
- }
- return nil
- }
- func (img *SQemuImage) String() string {
- return fmt.Sprintf("Qemu %s %d(%d) %s", img.Format, img.GetSizeMB(), img.GetActualSizeMB(), img.Path)
- }
- func (img *SQemuImage) String2ImageFormat() qemuimgfmt.TImageFormat {
- return qemuimgfmt.String2ImageFormat(string(img.Format))
- }
- func (img *SQemuImage) WholeChainFormatIs(format string) (bool, error) {
- if img.Format.String() != format {
- return false, nil
- }
- if len(img.BackFilePath) > 0 {
- backImg, err := NewQemuImage(img.BackFilePath)
- if err != nil {
- return false, err
- }
- return backImg.WholeChainFormatIs(format)
- }
- return true, nil
- }
- func (img *SQemuImage) Check() error {
- args := []string{"-c", strconv.Itoa(int(img.IoLevel)), qemutils.GetQemuImg(), "check"}
- info := SImageInfo{
- Path: img.Path,
- Format: img.Format,
- IoLevel: img.IoLevel,
- Password: img.Password,
- EncryptFormat: img.EncryptFormat,
- EncryptAlg: img.EncryptAlg,
- secId: "sec0",
- }
- if info.Encrypted() {
- args = append(args, "--object", info.SecretOptions())
- }
- args = append(args, "--image-opts", info.ImageOptions())
- cmd := procutils.NewRemoteCommandAsFarAsPossible("ionice", args...)
- if runtime.GOOS == "darwin" {
- args = args[2:]
- cmd = procutils.NewRemoteCommandAsFarAsPossible(args[0], args[1:]...)
- }
- output, err := cmd.Output()
- if err != nil {
- return errors.Wrapf(err, "check: %s", string(output))
- }
- return nil
- }
- // "json:{\"driver\":\"qcow2\",\"file\":{\"driver\":\"file\",\"filename\":\"/opt/cloud/workspace/disks/snapshots/72a2383d-e980-486f-816c-6c562e1757f3_snap/f39f225a-921f-492e-8fb6-0a4167d6ed91\"}}"
- func ParseQemuFilepath(pathInfo string) (string, error) {
- if strings.HasPrefix(pathInfo, "json:{") {
- pathJson, err := jsonutils.ParseString(pathInfo[len("json:"):])
- if err != nil {
- return "", errors.Wrap(err, "jsonutils.ParseString")
- }
- path, err := pathJson.GetString("file", "filename")
- if err != nil {
- return "", errors.Wrap(err, "GetString file.filename")
- }
- return path, nil
- } else {
- return pathInfo, nil
- }
- }
|