yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/proxmox/instance.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 proxmox
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"net/url"
    22  	"regexp"
    23  	"sort"
    24  	"strconv"
    25  	"strings"
    26  
    27  	"yunion.io/x/jsonutils"
    28  	"yunion.io/x/log"
    29  	"yunion.io/x/pkg/errors"
    30  	"yunion.io/x/pkg/util/osprofile"
    31  
    32  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    33  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    34  	"yunion.io/x/cloudmux/pkg/multicloud"
    35  )
    36  
    37  var (
    38  	rxIso            = regexp.MustCompile(`(.*?),media`)
    39  	rxDeviceID       = regexp.MustCompile(`\d+`)
    40  	rxDiskName       = regexp.MustCompile(`(virtio|scsi|sata|ide)\d+`)
    41  	rxDiskType       = regexp.MustCompile(`\D+`)
    42  	rxUnusedDiskName = regexp.MustCompile(`^(unused)\d+`)
    43  	rxNicName        = regexp.MustCompile(`net\d+`)
    44  	rxSerialName     = regexp.MustCompile(`serial\d+`)
    45  	rxUsbName        = regexp.MustCompile(`usb\d+`)
    46  )
    47  
    48  type (
    49  	QemuDevices     map[string]map[string]interface{}
    50  	QemuDevice      map[string]interface{}
    51  	QemuDeviceParam []string
    52  )
    53  
    54  type Intermediate struct {
    55  	HardwareAddress string `json:"hardware-address"`
    56  	IPAddresses     []struct {
    57  		IPAddress     string `json:"ip-address"`
    58  		IPAddressType string `json:"ip-address-type"`
    59  		Prefix        int    `json:"prefix"`
    60  	} `json:"ip-addresses"`
    61  	Name       string           `json:"name"`
    62  	Statistics map[string]int64 `json:"statistics"`
    63  }
    64  
    65  type VmBase struct {
    66  	Name         string `json:"name"`
    67  	Description  string `json:"Description"`
    68  	Tags         string `json:"tags"`
    69  	Args         string `json:"args"`
    70  	Bios         string `json:"bios"`
    71  	OnBoot       int    `json:"onboot"`
    72  	Startup      string `json:"startup"`
    73  	Tablet       int    `json:"tablet"`
    74  	Ostype       string `json:"ostype"`
    75  	Memory       int64  `json:"memory"`
    76  	Balloon      int64  `json:"balloon"`
    77  	Cores        int64  `json:"cores"`
    78  	Vcpus        int64  `json:"vcpus"`
    79  	Sockets      int64  `json:"sockets"`
    80  	Cpu          string `json:"cpu"`
    81  	Numa         int    `json:"numa"`
    82  	Hotplug      string `json:"hotplug"`
    83  	Boot         string `json:"boot"`
    84  	Bootdisk     string `json:"bootdisk"`
    85  	Kvm          int    `json:"kvm"`
    86  	Scsihw       string `json:"scsihw"`
    87  	Hookscript   string `json:"hookscript"`
    88  	Machine      string `json:"machine"`
    89  	Ide2         string `json:"ide2,omitempty"`
    90  	Ciuser       string `json:"ciuser"`
    91  	Cipassword   string `json:"cipassword"`
    92  	Cicustom     string `json:"cicustom"`
    93  	Searchdomain string `json:"searchdomain"`
    94  	Nameserver   string `json:"nameserver"`
    95  	Sshkeys      string `json:"sshkeys"`
    96  }
    97  
    98  type SInstance struct {
    99  	multicloud.SInstanceBase
   100  	ProxmoxTags
   101  
   102  	host *SHost
   103  
   104  	QemuNetworks []SInstanceNic
   105  	PowerState   string
   106  	Node         string
   107  
   108  	VmID            int         `json:"vmid"`
   109  	Name            string      `json:"name"`
   110  	Description     string      `json:"desc"`
   111  	Pool            string      `json:"pool,omitempty"`
   112  	Bios            string      `json:"bios"`
   113  	EFIDisk         QemuDevice  `json:"efidisk,omitempty"`
   114  	Machine         string      `json:"machine,omitempty"`
   115  	Onboot          bool        `json:"onboot"`
   116  	Startup         string      `json:"startup,omitempty"`
   117  	Tablet          bool        `json:"tablet"`
   118  	Agent           int         `json:"agent"`
   119  	Memory          int         `json:"memory"`
   120  	Balloon         int         `json:"balloon"`
   121  	QemuOs          string      `json:"ostype"`
   122  	QemuCores       int         `json:"cores"`
   123  	QemuSockets     int         `json:"sockets"`
   124  	QemuVcpus       int         `json:"vcpus"`
   125  	QemuCpu         string      `json:"cpu"`
   126  	QemuNuma        bool        `json:"numa"`
   127  	QemuKVM         bool        `json:"kvm"`
   128  	Hotplug         string      `json:"hotplug"`
   129  	QemuIso         string      `json:"iso"`
   130  	QemuPxe         bool        `json:"pxe"`
   131  	FullClone       *int        `json:"fullclone"`
   132  	Boot            string      `json:"boot"`
   133  	BootDisk        string      `json:"bootdisk,omitempty"`
   134  	Scsihw          string      `json:"scsihw,omitempty"`
   135  	QemuDisks       QemuDevices `json:"disk"`
   136  	QemuUnusedDisks QemuDevices `json:"unused_disk"`
   137  	QemuVga         QemuDevice  `json:"vga,omitempty"`
   138  	QemuSerials     QemuDevices `json:"serial,omitempty"`
   139  	QemuUsbs        QemuDevices `json:"usb,omitempty"`
   140  	Hookscript      string      `json:"hookscript,omitempty"`
   141  	Tags            string      `json:"tags"`
   142  	Args            string      `json:"args"`
   143  
   144  	// Deprecated single disk.
   145  	DiskSize    float64 `json:"diskGB"`
   146  	Storage     string  `json:"storage"`
   147  	StorageType string  `json:"storageType"` // virtio|scsi (cloud-init defaults to scsi)
   148  
   149  	// Deprecated single nic.
   150  	QemuNicModel string `json:"nic"`
   151  	QemuBridge   string `json:"bridge"`
   152  	QemuVlanTag  int    `json:"vlan"`
   153  	QemuMacAddr  string `json:"mac"`
   154  
   155  	// cloud-init options
   156  	CIuser     string `json:"ciuser"`
   157  	CIpassword string `json:"cipassword"`
   158  	CIcustom   string `json:"cicustom"`
   159  
   160  	Searchdomain string `json:"searchdomain"`
   161  	Nameserver   string `json:"nameserver"`
   162  	Sshkeys      string `json:"sshkeys"`
   163  }
   164  
   165  func (self *SInstance) GetName() string {
   166  	return self.Name
   167  }
   168  
   169  func (self *SInstance) GetId() string {
   170  	return strconv.Itoa(self.VmID)
   171  }
   172  
   173  func (self *SInstance) GetGlobalId() string {
   174  	return self.GetId()
   175  }
   176  
   177  func (self *SInstance) Refresh() error {
   178  	id := strconv.Itoa(int(self.VmID))
   179  	ins, err := self.host.zone.region.GetInstance(id)
   180  	if err != nil {
   181  		return err
   182  	}
   183  	return jsonutils.Update(self, ins)
   184  }
   185  
   186  func (self *SInstance) AssignSecurityGroup(id string) error {
   187  	return cloudprovider.ErrNotSupported
   188  }
   189  
   190  func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error {
   191  	return self.host.zone.region.AttachDisk(self.VmID, diskId)
   192  }
   193  
   194  func (self *SInstance) CreateDisk(ctx context.Context, opts *cloudprovider.GuestDiskCreateOptions) (string, error) {
   195  	return "", cloudprovider.ErrNotSupported
   196  }
   197  
   198  func (self *SInstance) ChangeConfig(ctx context.Context, opts *cloudprovider.SManagedVMChangeConfig) error {
   199  	return self.host.zone.region.ChangeConfig(self.VmID, opts.Cpu, opts.MemoryMB)
   200  }
   201  
   202  func (self *SInstance) DeleteVM(ctx context.Context) error {
   203  	return self.host.zone.region.DeleteVM(self.VmID)
   204  }
   205  
   206  func (self *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error {
   207  	return self.host.zone.region.ResetVmPassword(self.VmID, username, password)
   208  }
   209  
   210  func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error {
   211  	return self.host.zone.region.DetachDisk(self.VmID, diskId)
   212  }
   213  
   214  func (self *SInstance) GetBios() cloudprovider.TBiosType {
   215  	return cloudprovider.ToBiosType(self.Bios)
   216  }
   217  
   218  func (self *SInstance) GetBootOrder() string {
   219  	return strings.ToLower(self.Boot)
   220  }
   221  
   222  func (self *SInstance) GetError() error {
   223  	return nil
   224  }
   225  
   226  func (self *SInstance) GetHostname() string {
   227  	return self.GetName()
   228  }
   229  
   230  func (self *SInstance) GetHypervisor() string {
   231  	return api.HYPERVISOR_PROXMOX
   232  }
   233  
   234  func (self *SInstance) VMIdExists(vmId int) (bool, error) {
   235  	resources, err := self.host.zone.region.GetClusterVmResources()
   236  	if err != nil {
   237  		return false, err
   238  	}
   239  
   240  	_, res := resources[vmId]
   241  	return res, nil
   242  }
   243  
   244  func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
   245  	ret := []cloudprovider.ICloudDisk{}
   246  	id := self.VmID
   247  
   248  	exist, err := self.VMIdExists(self.VmID)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  
   253  	if exist == false {
   254  		return nil, nil
   255  	}
   256  
   257  	for k, v := range self.QemuDisks {
   258  		disk, err := self.host.zone.region.GetDisk(k)
   259  		if err != nil {
   260  			continue
   261  		}
   262  		disk.VmId = id
   263  		disk.DiskDriver = v["type"].(string)
   264  		disk.DriverIdx = v["slot"].(int)
   265  		if cache, ok := v["cache"].(string); ok {
   266  			disk.CacheMode = cache
   267  		}
   268  		ret = append(ret, disk)
   269  	}
   270  
   271  	// for k, v := range self.QemuUnusedDisks {
   272  	// 	disk, err := self.host.zone.region.GetDisk(k)
   273  	// 	if err != nil {
   274  	// 		continue
   275  	// 	}
   276  	// 	disk.VmId = self.VmID
   277  	// 	disk.DriverIdx = v["slot"].(int)
   278  	// 	ret = append(ret, disk)
   279  	// }
   280  
   281  	return ret, nil
   282  }
   283  
   284  func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
   285  	return nil, cloudprovider.ErrNotSupported
   286  }
   287  
   288  func (self *SInstance) GetIHost() cloudprovider.ICloudHost {
   289  	return self.host
   290  }
   291  
   292  func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
   293  	ret := []cloudprovider.ICloudNic{}
   294  	for i := range self.QemuNetworks {
   295  		self.QemuNetworks[i].ins = self
   296  		ret = append(ret, &self.QemuNetworks[i])
   297  	}
   298  	return ret, nil
   299  }
   300  
   301  func (self *SInstance) GetInstanceType() string {
   302  	return fmt.Sprintf("ecs.g1.c%dm%d", self.GetVcpuCount(), self.GetVmemSizeMB()/1024)
   303  }
   304  
   305  func (self *SInstance) GetMachine() string {
   306  	return self.Machine
   307  }
   308  
   309  func (self *SInstance) GetStatus() string {
   310  	switch strings.ToLower(self.PowerState) {
   311  	case "running":
   312  		return api.VM_RUNNING
   313  	case "stopped":
   314  		return api.VM_READY
   315  	case "paused":
   316  		return api.VM_SUSPEND
   317  	}
   318  	return api.VM_UNKNOWN
   319  }
   320  
   321  func (self *SInstance) GetFullOsName() string {
   322  	return ""
   323  }
   324  
   325  func (self *SInstance) GetOsType() cloudprovider.TOsType {
   326  	isWin, _ := regexp.MatchString("(wxp|w2k|w2k3|w2k8|wvista|win7|win8|win10|win11)", self.QemuOs)
   327  	if isWin == true {
   328  		return cloudprovider.TOsType(osprofile.OS_TYPE_WINDOWS)
   329  	} else {
   330  		return cloudprovider.TOsType(osprofile.OS_TYPE_LINUX)
   331  	}
   332  }
   333  
   334  func (ins *SInstance) GetOsArch() string {
   335  	return "x86_64"
   336  }
   337  
   338  func (ins *SInstance) GetOsDist() string {
   339  	return ""
   340  }
   341  
   342  func (ins *SInstance) GetOsVersion() string {
   343  	return ""
   344  }
   345  
   346  func (ins *SInstance) GetOsLang() string {
   347  	return ""
   348  }
   349  
   350  func (self *SInstance) GetProjectId() string {
   351  	return ""
   352  }
   353  
   354  func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
   355  	return nil, cloudprovider.ErrNotSupported
   356  }
   357  
   358  func (self *SInstance) GetVcpuCount() int {
   359  	return int(self.QemuCores * self.QemuSockets)
   360  }
   361  
   362  func (self *SInstance) GetVmemSizeMB() int {
   363  	return int(self.Memory)
   364  }
   365  
   366  func (self *SInstance) GetVga() string {
   367  	return "std"
   368  }
   369  
   370  func (self *SInstance) GetVdi() string {
   371  	return "vnc"
   372  }
   373  
   374  func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
   375  	return "", cloudprovider.ErrNotSupported
   376  }
   377  
   378  func (self *SInstance) GetSecurityGroupIds() ([]string, error) {
   379  	return []string{}, nil
   380  }
   381  
   382  func (self *SInstance) SetSecurityGroups(secgroupIds []string) error {
   383  	return cloudprovider.ErrNotSupported
   384  }
   385  
   386  func (self *SInstance) StartVM(ctx context.Context) error {
   387  	return self.host.zone.region.StartVm(self.VmID)
   388  }
   389  
   390  func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
   391  	if self.GetStatus() == api.VM_READY {
   392  		return nil
   393  	}
   394  	return self.host.zone.region.StopVm(self.VmID)
   395  }
   396  
   397  func (self *SInstance) UpdateUserData(userData string) error {
   398  	return cloudprovider.ErrNotSupported
   399  }
   400  
   401  func (self *SInstance) UpdateVM(ctx context.Context, name string) error {
   402  	return cloudprovider.ErrNotSupported
   403  }
   404  
   405  // readDeviceConfig - get standard sub-conf strings where `key=value` and update conf map.
   406  func (confMap QemuDevice) readDeviceConfig(confList []string) error {
   407  	// Add device config.
   408  	for _, conf := range confList {
   409  		key, value := ParseSubConf(conf, "=")
   410  		confMap[key] = value
   411  	}
   412  	return nil
   413  }
   414  
   415  func (self *SRegion) GetVmAgentNetworkInterfaces(node string, VmId int) (map[string]string, error) {
   416  	intermediates := []Intermediate{}
   417  	ipMap := map[string]string{}
   418  	res := fmt.Sprintf("/nodes/%s/qemu/%d/agent/network-get-interfaces", node, VmId)
   419  	err := self.getAgent(res, url.Values{}, &intermediates)
   420  	if err != nil {
   421  		return nil, errors.Wrap(err, "GetVmAgentNetworkInterfaces")
   422  	}
   423  
   424  	for _, intermediate := range intermediates {
   425  		ipMap[intermediate.HardwareAddress] = intermediate.IPAddresses[0].IPAddress
   426  	}
   427  
   428  	return ipMap, nil
   429  }
   430  
   431  func (self *SRegion) GetVmPowerStatus(node string, VmId int) string {
   432  	current := map[string]string{}
   433  	res := fmt.Sprintf("/nodes/%s/qemu/%d/status/current", node, VmId)
   434  	err := self.get(res, url.Values{}, &current)
   435  	if err != nil {
   436  		return "unkown"
   437  	}
   438  
   439  	power := "unkown"
   440  	if _, ok := current["qmpstatus"]; ok {
   441  		power = current["qmpstatus"]
   442  	}
   443  
   444  	return power
   445  }
   446  
   447  func (self *SRegion) GetQemuConfig(node string, VmId int) (*SInstance, error) {
   448  	//ret := &SInstance{}
   449  	res := fmt.Sprintf("/nodes/%s/qemu/%d/config", node, VmId)
   450  	vmConfig := map[string]interface{}{}
   451  	vmBase := &VmBase{
   452  		Bios:    "seabios",
   453  		OnBoot:  1,
   454  		Tablet:  1,
   455  		Ostype:  "other",
   456  		Memory:  0,
   457  		Balloon: 0,
   458  		Cores:   0,
   459  		Vcpus:   0,
   460  		Sockets: 0,
   461  		Cpu:     "host",
   462  		Numa:    0,
   463  		Hotplug: "network,disk,usb",
   464  		Boot:    "cdn",
   465  		Kvm:     1,
   466  		Scsihw:  "lsi",
   467  		Machine: "i440fx",
   468  	}
   469  	err := self.get(res, url.Values{}, &vmConfig)
   470  	if err != nil {
   471  		return nil, err
   472  	}
   473  	byteArr, err := json.Marshal(&vmConfig)
   474  	if err != nil {
   475  		return nil, err
   476  	}
   477  	err = json.Unmarshal(byteArr, &vmBase)
   478  	if err != nil {
   479  		return nil, err
   480  	}
   481  
   482  	config := SInstance{
   483  		VmID:            int(VmId),
   484  		Name:            vmBase.Name,
   485  		Description:     strings.TrimSpace(vmBase.Description),
   486  		Tags:            strings.TrimSpace(vmBase.Tags),
   487  		Args:            strings.TrimSpace(vmBase.Args),
   488  		Bios:            vmBase.Bios,
   489  		EFIDisk:         QemuDevice{},
   490  		Machine:         vmBase.Machine,
   491  		Onboot:          Itob(vmBase.OnBoot),
   492  		Startup:         vmBase.Startup,
   493  		Tablet:          Itob(vmBase.Tablet),
   494  		QemuOs:          vmBase.Ostype,
   495  		Memory:          int(vmBase.Memory),
   496  		QemuCores:       int(vmBase.Cores),
   497  		QemuSockets:     int(vmBase.Sockets),
   498  		QemuCpu:         vmBase.Cpu,
   499  		QemuNuma:        Itob(vmBase.Numa),
   500  		QemuKVM:         Itob(vmBase.Kvm),
   501  		Hotplug:         vmBase.Hotplug,
   502  		QemuVlanTag:     -1,
   503  		Boot:            vmBase.Boot,
   504  		BootDisk:        vmBase.Bootdisk,
   505  		Scsihw:          vmBase.Scsihw,
   506  		Hookscript:      vmBase.Hookscript,
   507  		QemuDisks:       QemuDevices{},
   508  		QemuUnusedDisks: QemuDevices{},
   509  		QemuVga:         QemuDevice{},
   510  		QemuNetworks:    []SInstanceNic{},
   511  		QemuSerials:     QemuDevices{},
   512  		QemuUsbs:        QemuDevices{},
   513  		Node:            node,
   514  		CIuser:          vmBase.Ciuser,
   515  		CIpassword:      vmBase.Cipassword,
   516  		Searchdomain:    vmBase.Searchdomain,
   517  		Nameserver:      vmBase.Nameserver,
   518  	}
   519  
   520  	// vmConfig Sample: map[ cpu:host
   521  	// net0:virtio=62:DF:XX:XX:XX:XX,bridge=vmbr0
   522  	// ide2:local:iso/xxx-xx.iso,media=cdrom memory:2048
   523  	// smbios1:uuid=8b3bf833-aad8-4545-xxx-xxxxxxx digest:aa6ce5xxxxx1b9ce33e4aaeff564d4 sockets:1
   524  	// name:terraform-ubuntu1404-template bootdisk:virtio0
   525  	// virtio0:ProxmoxxxxISCSI:vm-1014-disk-2,size=4G
   526  	// description:Base image
   527  	// cores:2 ostype:l26
   528  	if vmConfig["ide2"] != nil {
   529  		isoMatch := rxIso.FindStringSubmatch(vmConfig["ide2"].(string))
   530  		config.QemuIso = isoMatch[1]
   531  	}
   532  
   533  	if _, ok := vmConfig["sshkeys"]; ok {
   534  		config.Sshkeys, _ = url.PathUnescape(vmConfig["sshkeys"].(string))
   535  	}
   536  
   537  	agent := 0
   538  	if _, ok := vmConfig["agent"]; ok {
   539  		switch vmConfig["agent"].(type) {
   540  		case int64:
   541  			agent = int(vmConfig["agent"].(int64))
   542  		case string:
   543  			agentConfList := strings.Split(vmConfig["agent"].(string), ",")
   544  			agent, _ = strconv.Atoi(agentConfList[0])
   545  		}
   546  
   547  	}
   548  	config.Agent = agent
   549  
   550  	config.PowerState = self.GetVmPowerStatus(node, VmId)
   551  
   552  	// Add disks.
   553  	diskNames := []string{}
   554  	for k := range vmConfig {
   555  		if diskName := rxDiskName.FindStringSubmatch(k); len(diskName) > 0 {
   556  			diskNames = append(diskNames, diskName[0])
   557  		}
   558  	}
   559  
   560  	for _, diskName := range diskNames {
   561  		diskConfStr := vmConfig[diskName].(string)
   562  
   563  		id := rxDeviceID.FindStringSubmatch(diskName)
   564  		diskID, _ := strconv.Atoi(id[0])
   565  		diskType := rxDiskType.FindStringSubmatch(diskName)[0]
   566  
   567  		diskConfMap := ParsePMConf(diskConfStr, "volume")
   568  
   569  		if diskConfMap["volume"].(string) == "none" {
   570  			continue
   571  		}
   572  
   573  		diskConfMap["slot"] = diskID
   574  		diskConfMap["type"] = diskType
   575  
   576  		storageName, fileName := ParseSubConf(diskConfMap["volume"].(string), ":")
   577  		diskConfMap["storage"] = storageName
   578  		diskConfMap["file"] = fileName
   579  
   580  		volId := diskConfMap["volume"].(string)
   581  
   582  		// cloud-init disks not always have the size sent by the API, which results in a crash
   583  		if diskConfMap["size"] == nil && strings.Contains(fileName.(string), "cloudinit") {
   584  			diskConfMap["size"] = "4M" // default cloud-init disk size
   585  		}
   586  
   587  		var sizeInTerabytes = regexp.MustCompile(`[0-9]+T`)
   588  		// Convert to gigabytes if disk size was received in terabytes
   589  		matched := sizeInTerabytes.MatchString(diskConfMap["size"].(string))
   590  		if matched {
   591  			diskConfMap["size"] = fmt.Sprintf("%.0fG", DiskSizeGB(diskConfMap["size"]))
   592  		}
   593  
   594  		// And device config to disks map.
   595  		if len(diskConfMap) > 0 {
   596  			config.QemuDisks[volId] = diskConfMap
   597  		}
   598  	}
   599  
   600  	// Add unused disks
   601  	// unused0:local:100/vm-100-disk-1.qcow2
   602  	unusedDiskNames := []string{}
   603  	for k := range vmConfig {
   604  		// look for entries from the config in the format "unusedX:<storagepath>" where X is an integer
   605  		if unusedDiskName := rxUnusedDiskName.FindStringSubmatch(k); len(unusedDiskName) > 0 {
   606  			unusedDiskNames = append(unusedDiskNames, unusedDiskName[0])
   607  		}
   608  	}
   609  	if len(unusedDiskNames) > 0 {
   610  		log.Debugf("[DEBUG] unusedDiskNames: %v", unusedDiskNames)
   611  	}
   612  
   613  	for _, unusedDiskName := range unusedDiskNames {
   614  		unusedDiskConfStr := vmConfig[unusedDiskName].(string)
   615  		finalDiskConfMap := QemuDevice{}
   616  
   617  		// parse "unused0" to get the id '0' as an int
   618  		id := rxDeviceID.FindStringSubmatch(unusedDiskName)
   619  		slotID, err := strconv.Atoi(id[0])
   620  		if err != nil {
   621  			return nil, errors.Errorf("Unable to parse unused disk id from input string '%s' .", unusedDiskName)
   622  		}
   623  		finalDiskConfMap["slot"] = slotID
   624  
   625  		// parse the attributes from the unused disk
   626  		// extract the storage and file path from the unused disk entry
   627  		parsedUnusedDiskMap := ParsePMConf(unusedDiskConfStr, "storage+file")
   628  		storageName, fileName := ParseSubConf(parsedUnusedDiskMap["storage+file"].(string), ":")
   629  		finalDiskConfMap["storage"] = storageName
   630  		finalDiskConfMap["file"] = fileName
   631  		volId := parsedUnusedDiskMap["storage+file"].(string)
   632  
   633  		config.QemuUnusedDisks[volId] = finalDiskConfMap
   634  	}
   635  
   636  	//Display
   637  	if vga, ok := vmConfig["vga"]; ok {
   638  		vgaList := strings.Split(vga.(string), ",")
   639  		vgaMap := QemuDevice{}
   640  
   641  		// TODO: keep going if error?
   642  		err = vgaMap.readDeviceConfig(vgaList)
   643  		if err != nil {
   644  			log.Debugf("[ERROR] %q", err)
   645  		}
   646  		if len(vgaMap) > 0 {
   647  			config.QemuVga = vgaMap
   648  		}
   649  	}
   650  
   651  	// Add networks.
   652  	nicNames := []string{}
   653  	ipMap := make(map[string]string)
   654  	if config.PowerState == "running" && config.Agent == 1 {
   655  		ipMap, _ = self.GetVmAgentNetworkInterfaces(node, VmId)
   656  	}
   657  
   658  	for k := range vmConfig {
   659  		if nicName := rxNicName.FindStringSubmatch(k); len(nicName) > 0 {
   660  			nicNames = append(nicNames, nicName[0])
   661  		}
   662  	}
   663  
   664  	for _, nicName := range nicNames {
   665  		nicConfStr := vmConfig[nicName]
   666  		nicConfList := strings.Split(nicConfStr.(string), ",")
   667  
   668  		//id := rxDeviceID.FindStringSubmatch(nicName)
   669  		model, macaddr := ParseSubConf(nicConfList[0], "=")
   670  		_, network := ParseSubConf(nicConfList[1], "=")
   671  		//nicID := fmt.Sprintf("%d:%s", VmId, nicName)
   672  
   673  		// Add model and MAC address.
   674  		nicConf := SInstanceNic{
   675  			NicId:     network.(string),
   676  			Model:     model,
   677  			MacAddr:   strings.ToLower(macaddr.(string)),
   678  			NetworkId: fmt.Sprintf("network/%s/%s", node, network),
   679  		}
   680  
   681  		if ip, ok := ipMap[nicConf.MacAddr]; ok {
   682  			nicConf.IpAddr = ip
   683  		}
   684  
   685  		// And device config to networks.
   686  		config.QemuNetworks = append(config.QemuNetworks, nicConf)
   687  	}
   688  
   689  	// Add serials
   690  	serialNames := []string{}
   691  
   692  	for k := range vmConfig {
   693  		if serialName := rxSerialName.FindStringSubmatch(k); len(serialName) > 0 {
   694  			serialNames = append(serialNames, serialName[0])
   695  		}
   696  	}
   697  
   698  	for _, serialName := range serialNames {
   699  		id := rxDeviceID.FindStringSubmatch(serialName)
   700  		serialID, _ := strconv.Atoi(id[0])
   701  
   702  		serialConfMap := QemuDevice{
   703  			"id":   serialID,
   704  			"type": vmConfig[serialName],
   705  		}
   706  
   707  		// And device config to serials map.
   708  		if len(serialConfMap) > 0 {
   709  			config.QemuSerials[serialName] = serialConfMap
   710  		}
   711  	}
   712  
   713  	// Add usbs
   714  	usbNames := []string{}
   715  
   716  	for k := range vmConfig {
   717  		if usbName := rxUsbName.FindStringSubmatch(k); len(usbName) > 0 {
   718  			usbNames = append(usbNames, usbName[0])
   719  		}
   720  	}
   721  
   722  	for _, usbName := range usbNames {
   723  		usbConfStr := vmConfig[usbName]
   724  		usbConfList := strings.Split(usbConfStr.(string), ",")
   725  		id := rxDeviceID.FindStringSubmatch(usbName)
   726  		usbID, _ := strconv.Atoi(id[0])
   727  		_, host := ParseSubConf(usbConfList[0], "=")
   728  
   729  		usbConfMap := QemuDevice{
   730  			"id":   usbID,
   731  			"host": host,
   732  		}
   733  
   734  		err = usbConfMap.readDeviceConfig(usbConfList[1:])
   735  		if err != nil {
   736  			log.Debugf("[ERROR] %q", err)
   737  		}
   738  		if usbConfMap["usb3"] == 1 {
   739  			usbConfMap["usb3"] = true
   740  		}
   741  
   742  		// And device config to usbs map.
   743  		if len(usbConfMap) > 0 {
   744  			config.QemuUsbs[usbName] = usbConfMap
   745  		}
   746  	}
   747  
   748  	return &config, nil
   749  
   750  }
   751  
   752  func (self *SRegion) GetInstances(hostId string) ([]SInstance, error) {
   753  	ret := []SInstance{}
   754  	resources, err := self.GetClusterVmResources()
   755  	if err != nil {
   756  		return nil, err
   757  	}
   758  
   759  	for _, res := range resources {
   760  		if res.NodeId == hostId {
   761  			instance, err := self.GetQemuConfig(res.Node, res.VmId)
   762  			if err == nil {
   763  				ret = append(ret, *instance)
   764  			}
   765  
   766  		}
   767  	}
   768  
   769  	return ret, nil
   770  }
   771  
   772  func (self *SRegion) GetInstance(id string) (*SInstance, error) {
   773  	resources, err := self.GetClusterVmResources()
   774  	if err != nil {
   775  		return nil, err
   776  	}
   777  
   778  	nodeName := ""
   779  	vmId, _ := strconv.Atoi(id)
   780  	if resource, ok := resources[vmId]; !ok {
   781  		return nil, errors.Errorf("failed get Instance id %s", id)
   782  	} else {
   783  		nodeName = resource.Node
   784  	}
   785  
   786  	return self.GetQemuConfig(nodeName, vmId)
   787  }
   788  
   789  func (self *SRegion) StartVm(vmId int) error {
   790  	resources, err := self.GetClusterVmResources()
   791  	if err != nil {
   792  		return err
   793  	}
   794  
   795  	nodeName := ""
   796  	if resource, ok := resources[vmId]; !ok {
   797  		return errors.Errorf("start VM id %d", vmId)
   798  	} else {
   799  		nodeName = resource.Node
   800  	}
   801  
   802  	res := fmt.Sprintf("/nodes/%s/qemu/%d/status/start", nodeName, vmId)
   803  	params := url.Values{}
   804  
   805  	_, err = self.post(res, params)
   806  	return err
   807  
   808  }
   809  
   810  func (self *SRegion) StopVm(vmId int) error {
   811  	resources, err := self.GetClusterVmResources()
   812  	if err != nil {
   813  		return err
   814  	}
   815  
   816  	nodeName := ""
   817  	if resource, ok := resources[vmId]; !ok {
   818  		return errors.Errorf("start VM id %d", vmId)
   819  	} else {
   820  		nodeName = resource.Node
   821  	}
   822  
   823  	res := fmt.Sprintf("/nodes/%s/qemu/%d/status/stop", nodeName, vmId)
   824  	params := url.Values{}
   825  
   826  	_, err = self.post(res, params)
   827  	return err
   828  }
   829  
   830  func (self *SRegion) AttachDisk(vmId int, diskId string) error {
   831  	id := strconv.Itoa(int(vmId))
   832  	vm1, err := self.GetInstance(id)
   833  	if err != nil {
   834  		return errors.Wrapf(err, "GetInstance(%d)", vmId)
   835  	}
   836  	if _, ok := vm1.QemuUnusedDisks[diskId]; !ok {
   837  		return nil
   838  	}
   839  
   840  	slotsArr := []int{}
   841  	for _, v := range vm1.QemuDisks {
   842  		if v["type"] == "scsi" {
   843  			slotIdx := v["slot"].(int)
   844  			slotsArr = append(slotsArr, slotIdx)
   845  		}
   846  	}
   847  	sort.Ints(slotsArr)
   848  	minSlot := slotsArr[0]
   849  	for idx, _ := range slotsArr {
   850  		if slotsArr[idx] == minSlot {
   851  			minSlot++
   852  		} else {
   853  			break
   854  		}
   855  
   856  	}
   857  
   858  	body := map[string]string{}
   859  	params := url.Values{}
   860  	diskName := fmt.Sprintf("scsi%d", minSlot)
   861  	body[diskName] = diskId
   862  	res := fmt.Sprintf("/nodes/%s/qemu/%d/config", vm1.Node, vm1.VmID)
   863  	err = self.put(res, params, jsonutils.Marshal(body), nil)
   864  	if err != nil {
   865  		return errors.Wrapf(err, "GetInstance(%d) self.put", vmId)
   866  	}
   867  	//clear
   868  	vm1.QemuDisks = make(map[string]map[string]interface{})
   869  	vm1.QemuUnusedDisks = make(map[string]map[string]interface{})
   870  
   871  	vm2, err := self.GetInstance(id)
   872  	if err != nil {
   873  		return errors.Wrapf(err, "GetInstance(%d) vm2", vmId)
   874  	}
   875  	vm1.QemuDisks = vm2.QemuDisks
   876  	vm1.QemuUnusedDisks = vm2.QemuUnusedDisks
   877  
   878  	return nil
   879  
   880  }
   881  
   882  func (self *SRegion) DetachDisk(vmId int, diskId string) error {
   883  	id := strconv.Itoa(int(vmId))
   884  	vm1, err := self.GetInstance(id)
   885  	if err != nil {
   886  		return errors.Wrapf(err, "GetInstance(%d)", vmId)
   887  	}
   888  	if v, ok := vm1.QemuDisks[diskId]; !ok {
   889  		return nil
   890  	} else {
   891  		diskName := fmt.Sprintf("%s%d", v["type"].(string), v["slot"].(int))
   892  		body := map[string]string{}
   893  		params := url.Values{}
   894  		body["delete"] = diskName
   895  		res := fmt.Sprintf("/nodes/%s/qemu/%d/config", vm1.Node, vm1.VmID)
   896  		err := self.put(res, params, jsonutils.Marshal(body), nil)
   897  		if err != nil {
   898  			return errors.Wrapf(err, "GetInstance(%d) self.put", vmId)
   899  		}
   900  		//clear
   901  		vm1.QemuDisks = make(map[string]map[string]interface{})
   902  		vm1.QemuUnusedDisks = make(map[string]map[string]interface{})
   903  
   904  		vm2, err := self.GetInstance(id)
   905  		if err != nil {
   906  			return errors.Wrapf(err, "GetInstance(%d) vm2", vmId)
   907  		}
   908  		vm1.QemuDisks = vm2.QemuDisks
   909  		vm1.QemuUnusedDisks = vm2.QemuUnusedDisks
   910  
   911  		return nil
   912  	}
   913  
   914  }
   915  
   916  func (self *SRegion) ChangeConfig(vmId int, cpu int, memMb int) error {
   917  	vm, err := self.GetInstance(strconv.Itoa(int(vmId)))
   918  	body := map[string]interface{}{}
   919  	if err != nil {
   920  		return errors.Wrapf(err, "ChangeConfig(%d)", vmId)
   921  	}
   922  
   923  	changed := false
   924  	if cpu > 0 {
   925  		vm.QemuCores = 1
   926  		vm.QemuSockets = cpu
   927  		vm.QemuVcpus = cpu
   928  		changed = true
   929  		body["cores"] = 1
   930  		body["sockets"] = cpu
   931  		body["vcpus"] = cpu
   932  	}
   933  	if memMb > 0 {
   934  		vm.Memory = memMb
   935  		body["memory"] = memMb
   936  		changed = true
   937  	}
   938  	if !changed {
   939  		return nil
   940  	}
   941  
   942  	params := url.Values{}
   943  	res := fmt.Sprintf("/nodes/%s/qemu/%d/config", vm.Node, vmId)
   944  	return self.put(res, params, jsonutils.Marshal(body), nil)
   945  }
   946  
   947  func (self *SRegion) ResetVmPassword(vmId int, username, password string) error {
   948  	resources, err := self.GetClusterVmResources()
   949  	if err != nil {
   950  		return err
   951  	}
   952  
   953  	nodeName := ""
   954  	if resource, ok := resources[vmId]; !ok {
   955  		return errors.Errorf("failed to ResetVmPassword VM id %d", vmId)
   956  	} else {
   957  		nodeName = resource.Node
   958  	}
   959  
   960  	params := url.Values{}
   961  	body := map[string]interface{}{
   962  		"username": username,
   963  		"password": password,
   964  	}
   965  
   966  	res := fmt.Sprintf("/nodes/%s/qemu/%d/agent/set-user-password", nodeName, vmId)
   967  	return self.put(res, params, jsonutils.Marshal(body), nil)
   968  
   969  }
   970  
   971  func (self *SRegion) DeleteVM(vmId int) error {
   972  	id := strconv.Itoa(int(vmId))
   973  	vm1, err := self.GetInstance(id)
   974  	if err != nil {
   975  		return errors.Wrapf(err, "GetInstance(%d)", vmId)
   976  	}
   977  	params := url.Values{}
   978  	params.Set("purge", "1")
   979  
   980  	res := fmt.Sprintf("/nodes/%s/qemu/%d", vm1.Node, vmId)
   981  	return self.del(res, params, nil)
   982  }
   983  func (self *SRegion) GenVM(name, node string, cores, memMB int) (*SInstance, error) {
   984  
   985  	vmId := self.GetClusterVmMaxId()
   986  	if vmId == -1 {
   987  		return nil, errors.Errorf("failed to get vm number by %d", vmId)
   988  	} else {
   989  		vmId++
   990  	}
   991  
   992  	body := map[string]interface{}{
   993  		"vmid":        vmId,
   994  		"name":        name,
   995  		"ostype":      "other",
   996  		"sockets":     1,
   997  		"cores":       cores,
   998  		"cpu":         "host",
   999  		"kvm":         1,
  1000  		"hotplug":     "network,disk,usb",
  1001  		"memory":      memMB,
  1002  		"description": "",
  1003  		"scsihw":      "virtio-scsi-pci",
  1004  	}
  1005  
  1006  	res := fmt.Sprintf("/nodes/%s/qemu", node)
  1007  	_, err := self.post(res, jsonutils.Marshal(body))
  1008  	if err != nil {
  1009  		return nil, err
  1010  	}
  1011  
  1012  	vmIdRet := strconv.Itoa(vmId)
  1013  	vm, err := self.GetInstance(vmIdRet)
  1014  	if err != nil {
  1015  		return nil, err
  1016  	}
  1017  
  1018  	return vm, nil
  1019  }