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{}, ¤t) 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 }