yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/azure/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 azure 16 17 import ( 18 "context" 19 "fmt" 20 "net/url" 21 "strings" 22 "time" 23 24 "yunion.io/x/jsonutils" 25 "yunion.io/x/log" 26 "yunion.io/x/pkg/errors" 27 "yunion.io/x/pkg/util/osprofile" 28 29 "yunion.io/x/cloudmux/pkg/apis" 30 billing_api "yunion.io/x/cloudmux/pkg/apis/billing" 31 api "yunion.io/x/cloudmux/pkg/apis/compute" 32 "yunion.io/x/cloudmux/pkg/cloudprovider" 33 "yunion.io/x/cloudmux/pkg/multicloud" 34 "yunion.io/x/onecloud/pkg/util/billing" 35 "yunion.io/x/onecloud/pkg/util/version" 36 ) 37 38 const ( 39 DEFAULT_EXTENSION_NAME = "enablevmaccess" 40 ) 41 42 type HardwareProfile struct { 43 VMSize string `json:"vmSize,omitempty"` 44 } 45 46 type ImageReference struct { 47 Publisher string `json:"publisher,omitempty"` 48 Offer string `json:"offer,omitempty"` 49 Sku string `json:"sku,omitempty"` 50 Version string `json:"version,omitempty"` 51 ID string `json:"id,omitempty"` 52 } 53 54 type VirtualHardDisk struct { 55 Uri string `json:"uri,omitempty"` 56 } 57 58 type ManagedDiskParameters struct { 59 StorageAccountType string `json:"storageAccountType,omitempty"` 60 ID string 61 } 62 63 type StorageProfile struct { 64 ImageReference ImageReference `json:"imageReference,omitempty"` 65 OsDisk SOsDisk `json:"osDisk,omitempty"` 66 DataDisks []SDataDisk `json:"dataDisks,allowempty"` 67 } 68 69 type SSHPublicKey struct { 70 Path string `json:"path,omitempty"` 71 KeyData string `json:"keyData,omitempty"` 72 } 73 74 type SSHConfiguration struct { 75 PublicKeys []SSHPublicKey `json:"publicKeys,omitempty"` 76 } 77 78 type LinuxConfiguration struct { 79 DisablePasswordAuthentication bool `json:"disablePasswordAuthentication,omitempty"` 80 SSH *SSHConfiguration `json:"ssh,omitempty"` 81 } 82 83 type VaultCertificate struct { 84 CertificateURL string `json:"certificateURL,omitempty"` 85 CertificateStore string `json:"certificateStore,omitempty"` 86 } 87 88 type VaultSecretGroup struct { 89 SourceVault SubResource `json:"sourceVault,omitempty"` 90 VaultCertificates []VaultCertificate `json:"vaultCertificates,omitempty"` 91 } 92 93 type OsProfile struct { 94 ComputerName string `json:"computerName,omitempty"` 95 AdminUsername string `json:"adminUsername,omitempty"` 96 AdminPassword string `json:"adminPassword,omitempty"` 97 CustomData string `json:"customData,omitempty"` 98 LinuxConfiguration *LinuxConfiguration `json:"linuxConfiguration,omitempty"` 99 Secrets []VaultSecretGroup `json:"secrets,omitempty"` 100 } 101 102 type NetworkInterfaceReference struct { 103 ID string 104 } 105 106 type NetworkProfile struct { 107 NetworkInterfaces []NetworkInterfaceReference `json:"networkInterfaces,omitempty"` 108 } 109 110 type Statuses struct { 111 Code string 112 Level string 113 DisplayStatus string `json:"displayStatus,omitempty"` 114 Message string 115 //Time time.Time 116 } 117 118 type SVMAgent struct { 119 VmAgentVersion string `json:"vmAgentVersion,omitempty"` 120 Statuses []Statuses `json:"statuses,omitempty"` 121 } 122 123 type SExtension struct { 124 Name string 125 Type string 126 TypeHandlerVersion string `json:"typeHandlerVersion,omitempty"` 127 Statuses []Statuses `json:"statuses,omitempty"` 128 } 129 130 type VirtualMachineInstanceView struct { 131 Statuses []Statuses `json:"statuses,omitempty"` 132 VMAgent SVMAgent `json:"vmAgent,omitempty"` 133 Extensions []SExtension `json:"extensions,omitempty"` 134 } 135 136 type DomainName struct { 137 Id string 138 Name string 139 Type string 140 } 141 142 type DebugProfile struct { 143 BootDiagnosticsEnabled *bool `json:"bootDiagnosticsEnabled,omitempty"` 144 ConsoleScreenshotBlobUri string `json:"consoleScreenshotBlobUri,omitempty"` 145 SerialOutputBlobUri string `json:"serialOutputBlobUri,omitempty"` 146 } 147 148 type VirtualMachineProperties struct { 149 ProvisioningState string `json:"provisioningState,omitempty"` 150 InstanceView *VirtualMachineInstanceView `json:"instanceView,omitempty"` 151 DomainName *DomainName `json:"domainName,omitempty"` 152 HardwareProfile HardwareProfile `json:"hardwareProfile,omitempty"` 153 NetworkProfile NetworkProfile `json:"networkProfile,omitempty"` 154 StorageProfile StorageProfile `json:"storageProfile,omitempty"` 155 DebugProfile *DebugProfile `json:"debugProfile,omitempty"` 156 OsProfile OsProfile `json:"osProfile,omitempty"` 157 VmId string `json:"vmId,omitempty"` 158 TimeCreated time.Time `json:"timeCreated,omitempty"` 159 } 160 161 type SExtensionResourceProperties struct { 162 AutoUpgradeMinorVersion bool 163 ProvisioningState string 164 Publisher string 165 Type string 166 TypeHandlerVersion string 167 } 168 169 type SExtensionResource struct { 170 Id string 171 Name string 172 Type string 173 Location string 174 175 Properties SExtensionResourceProperties 176 } 177 178 type SInstance struct { 179 multicloud.SInstanceBase 180 AzureTags 181 host *SHost 182 183 Properties VirtualMachineProperties 184 ID string 185 Name string 186 Type string 187 Location string 188 vmSize *SVMSize 189 190 Resources []SExtensionResource 191 } 192 193 func (self *SRegion) GetInstance(instanceId string) (*SInstance, error) { 194 instance := SInstance{} 195 params := url.Values{} 196 params.Set("$expand", "instanceView") 197 return &instance, self.get(instanceId, params, &instance) 198 } 199 200 func (self *SRegion) GetInstanceScaleSets() ([]SInstance, error) { 201 instance := []SInstance{} 202 return instance, self.client.list("Microsoft.Compute/virtualMachineScaleSets", url.Values{}, &instance) 203 } 204 205 func (self *SRegion) GetInstances() ([]SInstance, error) { 206 result := []SInstance{} 207 resource := fmt.Sprintf("Microsoft.Compute/locations/%s/virtualMachines", self.Name) 208 err := self.client.list(resource, url.Values{}, &result) 209 if err != nil { 210 return nil, err 211 } 212 return result, nil 213 } 214 215 func (self *SRegion) doDeleteVM(instanceId string) error { 216 return self.del(instanceId) 217 } 218 219 func (self *SInstance) GetSecurityGroupIds() ([]string, error) { 220 secgroupIds := []string{} 221 if nics, err := self.getNics(); err == nil { 222 for _, nic := range nics { 223 if len(nic.Properties.NetworkSecurityGroup.ID) > 0 { 224 secgroupIds = append(secgroupIds, strings.ToLower(nic.Properties.NetworkSecurityGroup.ID)) 225 } 226 } 227 } 228 return secgroupIds, nil 229 } 230 231 func (self *SInstance) GetSysTags() map[string]string { 232 data := map[string]string{} 233 if osDistribution := self.Properties.StorageProfile.ImageReference.Publisher; len(osDistribution) > 0 { 234 data["os_distribution"] = osDistribution 235 } 236 if loginAccount := self.Properties.OsProfile.AdminUsername; len(loginAccount) > 0 { 237 data["login_account"] = loginAccount 238 } 239 if loginKey := self.Properties.OsProfile.AdminPassword; len(loginKey) > 0 { 240 data["login_key"] = loginKey 241 } 242 for _, res := range self.Resources { 243 if strings.HasSuffix(strings.ToLower(res.Id), "databricksbootstrap") { 244 data[apis.IS_SYSTEM] = "true" 245 break 246 } 247 } 248 return data 249 } 250 251 func (self *SInstance) GetTags() (map[string]string, error) { 252 return self.Tags, nil 253 } 254 255 func (self *SInstance) GetHypervisor() string { 256 return api.HYPERVISOR_AZURE 257 } 258 259 func (self *SInstance) GetInstanceType() string { 260 return self.Properties.HardwareProfile.VMSize 261 } 262 263 func (self *SInstance) WaitVMAgentReady() error { 264 status := "" 265 err := cloudprovider.Wait(time.Second*5, time.Minute*5, func() (bool, error) { 266 if self.Properties.InstanceView == nil { 267 self.Refresh() 268 return false, nil 269 } 270 271 for _, vmAgent := range self.Properties.InstanceView.VMAgent.Statuses { 272 status = vmAgent.DisplayStatus 273 if status == "Ready" { 274 break 275 } 276 log.Debugf("vmAgent %s status: %s waite for ready", self.Properties.InstanceView.VMAgent.VmAgentVersion, vmAgent.DisplayStatus) 277 } 278 if status == "Ready" { 279 return true, nil 280 } 281 return false, self.Refresh() 282 }) 283 if err != nil { 284 return errors.Wrapf(err, "waitting vmAgent ready, current status: %s", status) 285 } 286 return nil 287 288 } 289 290 func (self *SInstance) WaitEnableVMAccessReady() error { 291 if self.Properties.InstanceView == nil { 292 return fmt.Errorf("instance may not install VMAgent or VMAgent not running") 293 } 294 if len(self.Properties.InstanceView.VMAgent.VmAgentVersion) > 0 { 295 return self.WaitVMAgentReady() 296 } 297 298 for _, extension := range self.Properties.InstanceView.Extensions { 299 if extension.Name == "enablevmaccess" { 300 displayStatus := "" 301 for _, status := range extension.Statuses { 302 displayStatus = status.DisplayStatus 303 if displayStatus == "Provisioning succeeded" { 304 return nil 305 } 306 } 307 return self.host.zone.region.deleteExtension(self.ID, "enablevmaccess") 308 } 309 } 310 311 return fmt.Errorf("instance may not install VMAgent or VMAgent not running") 312 } 313 314 func (self *SInstance) getNics() ([]SInstanceNic, error) { 315 nics := []SInstanceNic{} 316 for _, _nic := range self.Properties.NetworkProfile.NetworkInterfaces { 317 nic, err := self.host.zone.region.GetNetworkInterface(_nic.ID) 318 if err != nil { 319 return nil, errors.Wrapf(err, "GetNetworkInterface(%s)", _nic.ID) 320 } 321 nic.instance = self 322 nics = append(nics, *nic) 323 } 324 return nics, nil 325 } 326 327 func (self *SInstance) Refresh() error { 328 instance, err := self.host.zone.region.GetInstance(self.ID) 329 if err != nil { 330 return err 331 } 332 err = jsonutils.Update(self, instance) 333 if err != nil { 334 return err 335 } 336 self.Tags = instance.Tags 337 return nil 338 } 339 340 func (self *SInstance) GetStatus() string { 341 if self.Properties.InstanceView == nil { 342 err := self.Refresh() 343 if err != nil { 344 log.Errorf("failed to get status for instance %s", self.Name) 345 return api.VM_UNKNOWN 346 } 347 } 348 for _, statuses := range self.Properties.InstanceView.Statuses { 349 if code := strings.Split(statuses.Code, "/"); len(code) == 2 { 350 if code[0] == "PowerState" { 351 switch code[1] { 352 case "stopped", "deallocated": 353 return api.VM_READY 354 case "running": 355 return api.VM_RUNNING 356 case "stopping": 357 return api.VM_STOPPING 358 case "starting": 359 return api.VM_STARTING 360 case "deleting": 361 return api.VM_DELETING 362 default: 363 log.Errorf("Unknow instance status %s", code[1]) 364 return api.VM_UNKNOWN 365 } 366 } 367 } 368 if statuses.Level == "Error" { 369 log.Errorf("Find error code: [%s] message: %s", statuses.Code, statuses.Message) 370 } 371 } 372 return api.VM_UNKNOWN 373 } 374 375 func (self *SInstance) GetIHost() cloudprovider.ICloudHost { 376 return self.host 377 } 378 379 func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error { 380 return self.host.zone.region.AttachDisk(self.ID, diskId) 381 } 382 383 func (region *SRegion) AttachDisk(instanceId, diskId string) error { 384 instance, err := region.GetInstance(instanceId) 385 if err != nil { 386 return errors.Wrapf(err, "GetInstance(%s)", instanceId) 387 } 388 lunMaps := map[int32]bool{} 389 dataDisks := jsonutils.NewArray() 390 for _, disk := range instance.Properties.StorageProfile.DataDisks { 391 if disk.ManagedDisk != nil && strings.ToLower(disk.ManagedDisk.ID) == strings.ToLower(diskId) { 392 return nil 393 } 394 lunMaps[disk.Lun] = true 395 dataDisks.Add(jsonutils.Marshal(disk)) 396 } 397 lun := func() int32 { 398 idx := int32(0) 399 for { 400 if _, ok := lunMaps[idx]; !ok { 401 return idx 402 } 403 idx++ 404 } 405 }() 406 dataDisks.Add(jsonutils.Marshal(map[string]interface{}{ 407 "Lun": lun, 408 "CreateOption": "Attach", 409 "ManagedDisk": map[string]string{ 410 "Id": diskId, 411 }, 412 })) 413 params := jsonutils.NewDict() 414 params.Add(dataDisks, "Properties", "StorageProfile", "DataDisks") 415 params.Add(jsonutils.Marshal(instance.Properties.StorageProfile.OsDisk), "Properties", "StorageProfile", "OsDisk") 416 _, err = region.patch(instanceId, params) 417 return err 418 } 419 420 func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error { 421 return self.host.zone.region.DetachDisk(self.ID, diskId) 422 } 423 424 func (region *SRegion) DetachDisk(instanceId, diskId string) error { 425 instance, err := region.GetInstance(instanceId) 426 if err != nil { 427 return errors.Wrapf(err, "GetInstance(%s)", instanceId) 428 } 429 diskMaps := map[string]bool{} 430 dataDisks := jsonutils.NewArray() 431 for _, disk := range instance.Properties.StorageProfile.DataDisks { 432 if disk.ManagedDisk != nil { 433 diskMaps[strings.ToLower(disk.ManagedDisk.ID)] = true 434 if strings.ToLower(disk.ManagedDisk.ID) == strings.ToLower(diskId) { 435 continue 436 } 437 } 438 dataDisks.Add(jsonutils.Marshal(disk)) 439 } 440 if _, ok := diskMaps[strings.ToLower(diskId)]; !ok { 441 log.Warningf("not find disk %s with instance %s", diskId, instance.Name) 442 return nil 443 } 444 params := jsonutils.NewDict() 445 params.Add(dataDisks, "Properties", "StorageProfile", "DataDisks") 446 params.Add(jsonutils.Marshal(instance.Properties.StorageProfile.OsDisk), "Properties", "StorageProfile", "OsDisk") 447 _, err = region.patch(instanceId, params) 448 return err 449 } 450 451 func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error { 452 if len(config.InstanceType) > 0 { 453 return self.host.zone.region.ChangeConfig(self.ID, config.InstanceType) 454 } 455 var err error 456 for _, vmSize := range self.host.zone.region.getHardwareProfile(config.Cpu, config.MemoryMB) { 457 log.Debugf("Try HardwareProfile : %s", vmSize) 458 err = self.host.zone.region.ChangeConfig(self.ID, vmSize) 459 if err == nil { 460 return nil 461 } 462 } 463 if err != nil { 464 return errors.Wrap(err, "ChangeConfig") 465 } 466 return fmt.Errorf("Failed to change vm config, specification not supported") 467 } 468 469 func (self *SRegion) ChangeConfig(instanceId, instanceType string) error { 470 params := map[string]interface{}{ 471 "Properties": map[string]interface{}{ 472 "HardwareProfile": map[string]string{ 473 "vmSize": instanceType, 474 }, 475 }, 476 } 477 log.Debugf("Try HardwareProfile : %s", instanceType) 478 _, err := self.patch(instanceId, jsonutils.Marshal(params)) 479 return err 480 } 481 482 func (region *SRegion) ChangeVMConfig(ctx context.Context, instanceId string, ncpu int, vmem int) error { 483 instacen, err := region.GetInstance(instanceId) 484 if err != nil { 485 return err 486 } 487 return instacen.ChangeConfig(ctx, &cloudprovider.SManagedVMChangeConfig{Cpu: ncpu, MemoryMB: vmem}) 488 } 489 490 func (self *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error { 491 if len(publicKey) > 0 || len(password) > 0 { 492 // 先判断系统是否安装了vmAgent,然后等待扩展准备完成后再重置密码 493 err := self.WaitEnableVMAccessReady() 494 if err != nil { 495 return err 496 } 497 } 498 return self.host.zone.region.DeployVM(ctx, self.ID, string(self.GetOsType()), name, password, publicKey, deleteKeypair, description) 499 } 500 501 type VirtualMachineExtensionProperties struct { 502 Publisher string `json:"publisher,omitempty"` 503 Type string `json:"type,omitempty"` 504 TypeHandlerVersion string `json:"typeHandlerVersion,omitempty"` 505 ProtectedSettings interface{} `json:"protectedSettings,omitempty"` 506 Settings interface{} `json:"settings,omitempty"` 507 } 508 509 type SVirtualMachineExtension struct { 510 Location string `json:"location,omitempty"` 511 Properties VirtualMachineExtensionProperties `json:"properties,omitempty"` 512 } 513 514 func (region *SRegion) execOnLinux(instanceId string, command string) error { 515 extension := SVirtualMachineExtension{ 516 Location: region.Name, 517 Properties: VirtualMachineExtensionProperties{ 518 Publisher: "Microsoft.Azure.Extensions", 519 Type: "CustomScript", 520 TypeHandlerVersion: "2.0", 521 Settings: map[string]string{"commandToExecute": command}, 522 }, 523 } 524 resource := fmt.Sprintf("%s/extensions/CustomScript", instanceId) 525 _, err := region.put(resource, jsonutils.Marshal(extension)) 526 return err 527 } 528 529 func (region *SRegion) resetOvsEnv(instanceId string) error { 530 ovsEnv, err := region.getOvsEnv(instanceId) 531 if err != nil { 532 return err 533 } 534 err = region.execOnLinux(instanceId, fmt.Sprintf(`echo '%s' > /var/lib/waagent/ovf-env.xml`, ovsEnv)) 535 if err != nil { 536 return err 537 } 538 return region.execOnLinux(instanceId, "systemctl restart wagent") 539 } 540 541 func (region *SRegion) deleteExtension(instanceId, extensionName string) error { 542 return region.del(fmt.Sprintf("%s/extensions/%s", instanceId, extensionName)) 543 } 544 func (region *SRegion) resetLoginInfo(osType, instanceId string, setting map[string]interface{}) error { 545 // https://github.com/Azure/azure-linux-extensions/blob/master/VMAccess/README.md 546 handlerVersion := "1.5" 547 properties := map[string]interface{}{ 548 "Publisher": "Microsoft.OSTCExtensions", 549 "Type": "VMAccessForLinux", 550 "TypeHandlerVersion": handlerVersion, 551 "Settings": map[string]string{}, 552 "protectedSettings": setting, 553 554 "autoUpgradeMinorVersion": true, 555 } 556 if osType == osprofile.OS_TYPE_WINDOWS { 557 // https://github.com/Azure/azure-cli/blob/dev/src/azure-cli/azure/cli/command_modules/vm/custom.py 558 handlerVersion = "2.4" 559 properties["TypeHandlerVersion"] = handlerVersion 560 properties["Publisher"] = "Microsoft.Compute" 561 properties["Type"] = "VMAccessAgent" 562 } 563 params := map[string]interface{}{ 564 "Location": region.Name, 565 "Properties": properties, 566 } 567 instance, err := region.GetInstance(instanceId) 568 if err != nil { 569 return errors.Wrapf(err, "GetInstance(%s)", instanceId) 570 } 571 for _, extension := range instance.Resources { 572 if extension.Name == "enablevmaccess" { 573 if version.GT(extension.Properties.TypeHandlerVersion, handlerVersion) { 574 properties["TypeHandlerVersion"] = extension.Properties.TypeHandlerVersion 575 break 576 } 577 } 578 } 579 resource := fmt.Sprintf("%s/extensions/enablevmaccess", instanceId) 580 _, err = region.put(resource, jsonutils.Marshal(params)) 581 if err != nil { 582 switch osType { 583 case osprofile.OS_TYPE_WINDOWS: 584 return err 585 default: 586 err = region.deleteExtension(instanceId, "enablevmaccess") 587 if err != nil { 588 return err 589 } 590 err = region.resetOvsEnv(instanceId) 591 if err != nil { 592 return err 593 } 594 resource := fmt.Sprintf("%s/extensions/enablevmaccess", instanceId) 595 _, err = region.put(resource, jsonutils.Marshal(params)) 596 return err 597 } 598 } 599 err = cloudprovider.Wait(time.Second*5, time.Minute*5, func() (bool, error) { 600 instance, err := region.GetInstance(instanceId) 601 if err != nil { 602 return false, errors.Wrapf(err, "GetInstance(%s)", instanceId) 603 } 604 for _, extension := range instance.Resources { 605 if extension.Name == "enablevmaccess" { 606 if extension.Properties.ProvisioningState == "Succeeded" { 607 return true, nil 608 } 609 log.Debugf("enablevmaccess status %s expect Succeeded", extension.Properties.ProvisioningState) 610 if extension.Properties.ProvisioningState == "Failed" { 611 if instance.Properties.InstanceView != nil { 612 for _, info := range instance.Properties.InstanceView.Extensions { 613 if info.Name == "enablevmaccess" && len(info.Statuses) > 0 { 614 return false, fmt.Errorf("details: %s", jsonutils.Marshal(info.Statuses)) 615 } 616 } 617 } 618 return false, fmt.Errorf("reset passwod failed") 619 } 620 } 621 } 622 return false, nil 623 }) 624 if err != nil { 625 return errors.Wrapf(err, "wait for enablevmaccess error: %v", err) 626 } 627 return nil 628 } 629 630 func (region *SRegion) resetPublicKey(osType, instanceId string, username, publicKey string) error { 631 setting := map[string]interface{}{ 632 "username": username, 633 "ssh_key": publicKey, 634 } 635 return region.resetLoginInfo(osType, instanceId, setting) 636 } 637 638 func (region *SRegion) resetPassword(osType, instanceId, username, password string) error { 639 setting := map[string]interface{}{ 640 "username": username, 641 "password": password, 642 } 643 return region.resetLoginInfo(osType, instanceId, setting) 644 } 645 646 func (region *SRegion) DeployVM(ctx context.Context, instanceId, osType, name, password, publicKey string, deleteKeypair bool, description string) error { 647 instance, err := region.GetInstance(instanceId) 648 if err != nil { 649 return err 650 } 651 if deleteKeypair { 652 return nil 653 } 654 if len(publicKey) > 0 { 655 return region.resetPublicKey(osType, instanceId, instance.Properties.OsProfile.AdminUsername, publicKey) 656 } 657 if len(password) > 0 { 658 return region.resetPassword(osType, instanceId, instance.Properties.OsProfile.AdminUsername, password) 659 } 660 return nil 661 } 662 663 func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) { 664 cpu := self.GetVcpuCount() 665 memoryMb := self.GetVmemSizeMB() 666 opts := &cloudprovider.ServerStopOptions{ 667 IsForce: true, 668 } 669 self.StopVM(ctx, opts) 670 return self.host.zone.region.ReplaceSystemDisk(self, cpu, memoryMb, desc.ImageId, desc.Password, desc.PublicKey, desc.SysSizeGB) 671 } 672 673 func (region *SRegion) ReplaceSystemDisk(instance *SInstance, cpu int, memoryMb int, imageId, passwd, publicKey string, sysSizeGB int) (string, error) { 674 log.Debugf("ReplaceSystemDisk %s image: %s", instance.ID, imageId) 675 storageType := instance.Properties.StorageProfile.OsDisk.ManagedDisk.StorageAccountType 676 if len(storageType) == 0 { 677 _disk, err := region.GetDisk(instance.Properties.StorageProfile.OsDisk.ManagedDisk.ID) 678 if err != nil { 679 return "", err 680 } 681 storageType = _disk.Sku.Name 682 } 683 image, err := region.GetImageById(imageId) 684 if err != nil { 685 return "", errors.Wrapf(err, "GetImageById(%s)", imageId) 686 } 687 if minOsDiskSizeGB := image.GetMinOsDiskSizeGb(); minOsDiskSizeGB > sysSizeGB { 688 sysSizeGB = minOsDiskSizeGB 689 } 690 osType := instance.Properties.StorageProfile.OsDisk.OsType 691 // https://support.microsoft.com/zh-cn/help/4018933/the-default-size-of-windows-server-images-in-azure-is-changed-from-128 692 // windows默认系统盘是128G, 若重装系统时,系统盘小于128G,则会出现 {"error":{"code":"ResizeDiskError","message":"Disk size reduction is not supported. Current size is 137438953472 bytes, requested size is 33285996544 bytes.","target":"osDisk.diskSizeGB"}} 错误 693 if osType == osprofile.OS_TYPE_WINDOWS && sysSizeGB < 128 { 694 sysSizeGB = 128 695 } 696 nicId := instance.Properties.NetworkProfile.NetworkInterfaces[0].ID 697 nic, err := region.GetNetworkInterface(nicId) 698 if err != nil { 699 return "", errors.Wrapf(err, "GetNetworkInterface(%s)", nicId) 700 } 701 if len(nic.Properties.IPConfigurations) == 0 { 702 return "", fmt.Errorf("failed to find networkId for nic %s", nicId) 703 } 704 networkId := nic.Properties.IPConfigurations[0].Properties.Subnet.ID 705 706 nic, err = region.CreateNetworkInterface("", fmt.Sprintf("%s-temp-ifconfig", instance.Name), "", networkId, "") 707 if err != nil { 708 return "", errors.Wrapf(err, "CreateNetworkInterface") 709 } 710 711 newInstance, err := region.CreateInstanceSimple(instance.Name+"-rebuild", imageId, osType, cpu, memoryMb, sysSizeGB, storageType, []int{}, nic.ID, passwd, publicKey) 712 newInstance.host = instance.host 713 cloudprovider.Wait(time.Second*10, time.Minute*5, func() (bool, error) { 714 err = newInstance.WaitVMAgentReady() 715 if err != nil { 716 log.Warningf("WaitVMAgentReady for %s error: %v", newInstance.Name, err) 717 return false, nil 718 } 719 return true, nil 720 }) 721 722 opts := &cloudprovider.ServerStopOptions{ 723 IsForce: true, 724 } 725 newInstance.StopVM(context.Background(), opts) 726 727 pruneDiskId := instance.Properties.StorageProfile.OsDisk.ManagedDisk.ID 728 newDiskId := newInstance.Properties.StorageProfile.OsDisk.ManagedDisk.ID 729 730 err = newInstance.deleteVM(context.TODO(), true) 731 if err != nil { 732 log.Warningf("delete vm %s error: %v", newInstance.ID, err) 733 } 734 735 defer func() { 736 if len(pruneDiskId) > 0 { 737 err := cloudprovider.Wait(time.Second*3, time.Minute, func() (bool, error) { 738 err = region.DeleteDisk(pruneDiskId) 739 if err != nil { 740 log.Errorf("delete prune disk %s error: %v", pruneDiskId, err) 741 return false, nil 742 } 743 return true, nil 744 }) 745 if err != nil { 746 log.Errorf("timeout for delete prune disk %s", pruneDiskId) 747 } 748 } 749 }() 750 751 //交换系统盘 752 params := map[string]interface{}{ 753 "Id": instance.ID, 754 "Location": instance.Location, 755 "Properties": map[string]interface{}{ 756 "StorageProfile": map[string]interface{}{ 757 "OsDisk": map[string]interface{}{ 758 "createOption": "FromImage", 759 "ManagedDisk": map[string]interface{}{ 760 "Id": newDiskId, 761 "storageAccountType": nil, 762 }, 763 "osType": osType, 764 "DiskSizeGB": nil, 765 }, 766 }, 767 }, 768 } 769 err = region.update(jsonutils.Marshal(params), nil) 770 if err != nil { 771 // 更新失败,需要删除新建的系统盘 772 pruneDiskId = newDiskId 773 return "", errors.Wrapf(err, "region.update") 774 } 775 return strings.ToLower(newDiskId), nil 776 } 777 778 func (self *SInstance) UpdateVM(ctx context.Context, name string) error { 779 return cloudprovider.ErrNotSupported 780 } 781 782 func (self *SInstance) GetId() string { 783 return self.ID 784 } 785 786 func (self *SInstance) GetName() string { 787 return self.Name 788 } 789 790 func (self *SInstance) GetHostname() string { 791 return self.Name 792 } 793 794 func (self *SInstance) GetGlobalId() string { 795 return strings.ToLower(self.ID) 796 } 797 798 func (self *SRegion) DeleteVM(instanceId string) error { 799 return self.doDeleteVM(instanceId) 800 } 801 802 func (self *SInstance) deleteVM(ctx context.Context, keepSysDisk bool) error { 803 sysDiskId := "" 804 if self.Properties.StorageProfile.OsDisk.ManagedDisk != nil { 805 sysDiskId = self.Properties.StorageProfile.OsDisk.ManagedDisk.ID 806 } 807 err := self.host.zone.region.DeleteVM(self.ID) 808 if err != nil { 809 return err 810 } 811 if len(sysDiskId) > 0 && !keepSysDisk { 812 err := self.host.zone.region.DeleteDisk(sysDiskId) 813 if err != nil { 814 return err 815 } 816 } 817 818 nics, err := self.getNics() 819 if err != nil { 820 return err 821 } 822 for _, nic := range nics { 823 if err := nic.Delete(); err != nil { 824 if err != cloudprovider.ErrNotFound { 825 return err 826 } 827 } 828 } 829 return nil 830 } 831 832 func (self *SInstance) DeleteVM(ctx context.Context) error { 833 return self.deleteVM(ctx, false) 834 } 835 836 func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { 837 disks := []cloudprovider.ICloudDisk{} 838 self.Properties.StorageProfile.OsDisk.region = self.host.zone.region 839 disks = append(disks, &self.Properties.StorageProfile.OsDisk) 840 for i := range self.Properties.StorageProfile.DataDisks { 841 self.Properties.StorageProfile.DataDisks[i].region = self.host.zone.region 842 disks = append(disks, &self.Properties.StorageProfile.DataDisks[i]) 843 } 844 return disks, nil 845 } 846 847 func (self *SInstance) GetOsType() cloudprovider.TOsType { 848 return cloudprovider.TOsType(osprofile.NormalizeOSType(string(self.Properties.StorageProfile.OsDisk.OsType))) 849 } 850 851 func (self *SInstance) GetFullOsName() string { 852 return self.Properties.StorageProfile.ImageReference.Offer 853 } 854 855 func (self *SInstance) GetBios() cloudprovider.TBiosType { 856 return "BIOS" 857 } 858 859 func (i *SInstance) GetOsDist() string { 860 return "" 861 } 862 863 func (i *SInstance) GetOsVersion() string { 864 return "" 865 } 866 867 func (i *SInstance) GetOsLang() string { 868 return "" 869 } 870 871 func (i *SInstance) GetOsArch() string { 872 return apis.OS_ARCH_X86_64 873 } 874 875 func (self *SRegion) getOvsEnv(instanceId string) (string, error) { 876 instance, err := self.GetInstance(instanceId) 877 if err != nil { 878 return "", err 879 } 880 kms := map[string]string{ 881 "AzureGermanCloud": "kms.core.cloudapi.de", 882 "AzureChinaCloud": "kms.core.chinacloudapi.cn", 883 "AzureUSGovernmentCloud": "kms.core.usgovcloudapi.net", 884 "AzurePublicCloud": "kms.core.windows.net", 885 } 886 kmsServer := "kms.core.chinacloudapi.cn" 887 if _kmsServer, ok := kms[self.client.envName]; ok { 888 kmsServer = _kmsServer 889 } 890 return fmt.Sprintf(` 891 <ns0:Environment xmlns:ns0="http://schemas.dmtf.org/ovf/environment/1" xmlns:ns1="http://schemas.microsoft.com/windowsazure" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 892 <ns1:ProvisioningSection> 893 <ns1:Version>1.0</ns1:Version> 894 <ns1:LinuxProvisioningConfigurationSet> 895 <ns1:ConfigurationSetType>LinuxProvisioningConfiguration</ns1:ConfigurationSetType> 896 <ns1:UserName>%s</ns1:UserName> 897 <ns1:DisableSshPasswordAuthentication>false</ns1:DisableSshPasswordAuthentication> 898 <ns1:HostName>%s</ns1:HostName> 899 <ns1:UserPassword>REDACTED</ns1:UserPassword> 900 </ns1:LinuxProvisioningConfigurationSet> 901 </ns1:ProvisioningSection> 902 <ns1:PlatformSettingsSection> 903 <ns1:Version>1.0</ns1:Version> 904 <ns1:PlatformSettings> 905 <ns1:KmsServerHostname>%s</ns1:KmsServerHostname> 906 <ns1:ProvisionGuestAgent>true</ns1:ProvisionGuestAgent> 907 <ns1:GuestAgentPackageName xsi:nil="true" /> 908 <ns1:RetainWindowsPEPassInUnattend>true</ns1:RetainWindowsPEPassInUnattend> 909 <ns1:RetainOfflineServicingPassInUnattend>true</ns1:RetainOfflineServicingPassInUnattend> 910 <ns1:PreprovisionedVm>false</ns1:PreprovisionedVm> 911 <ns1:EnableTrustedImageIdentifier>false</ns1:EnableTrustedImageIdentifier> 912 </ns1:PlatformSettings> 913 </ns1:PlatformSettingsSection> 914 </ns0:Environment>`, instance.Properties.OsProfile.AdminUsername, instance.Properties.OsProfile.ComputerName, kmsServer), nil 915 } 916 917 func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) { 918 nics := make([]cloudprovider.ICloudNic, 0) 919 _nics, err := self.getNics() 920 if err != nil { 921 return nil, err 922 } 923 for i := 0; i < len(_nics); i++ { 924 _nics[i].instance = self 925 nics = append(nics, &_nics[i]) 926 } 927 return nics, nil 928 } 929 930 func (self *SInstance) GetMachine() string { 931 return "pc" 932 } 933 934 func (self *SInstance) GetBootOrder() string { 935 return "dcn" 936 } 937 938 func (self *SInstance) GetVga() string { 939 return "std" 940 } 941 942 func (self *SInstance) GetVdi() string { 943 return "vnc" 944 } 945 946 func (self *SInstance) fetchVMSize() error { 947 if self.vmSize == nil { 948 vmSize, err := self.host.zone.region.getVMSize(self.Properties.HardwareProfile.VMSize) 949 if err != nil { 950 return err 951 } 952 self.vmSize = vmSize 953 } 954 return nil 955 } 956 957 func (self *SInstance) GetVcpuCount() int { 958 err := self.fetchVMSize() 959 if err != nil { 960 log.Errorf("fetchVMSize error: %v", err) 961 return 0 962 } 963 return self.vmSize.NumberOfCores 964 } 965 966 func (self *SInstance) GetVmemSizeMB() int { 967 err := self.fetchVMSize() 968 if err != nil { 969 log.Errorf("fetchVMSize error: %v", err) 970 return 0 971 } 972 return int(self.vmSize.MemoryInMB) 973 } 974 975 func (self *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) { 976 return nil, cloudprovider.ErrNotSupported 977 } 978 979 func (self *SRegion) StartVM(instanceId string) error { 980 _, err := self.perform(instanceId, "start", nil) 981 return err 982 } 983 984 func (self *SInstance) StartVM(ctx context.Context) error { 985 if err := self.host.zone.region.StartVM(self.ID); err != nil { 986 return err 987 } 988 self.host.zone.region.patch(self.ID, jsonutils.Marshal(self)) 989 return cloudprovider.WaitStatus(self, api.VM_RUNNING, 10*time.Second, 300*time.Second) 990 } 991 992 func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { 993 err := self.host.zone.region.StopVM(self.ID, opts.IsForce) 994 if err != nil { 995 return err 996 } 997 self.host.zone.region.patch(self.ID, jsonutils.Marshal(self)) 998 return cloudprovider.WaitStatus(self, api.VM_READY, 10*time.Second, 300*time.Second) 999 } 1000 1001 func (self *SRegion) StopVM(instanceId string, isForce bool) error { 1002 _, err := self.perform(instanceId, "deallocate", nil) 1003 return err 1004 } 1005 1006 func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) { 1007 nics, err := self.getNics() 1008 if err != nil { 1009 return nil, err 1010 } 1011 for _, nic := range nics { 1012 for _, ip := range nic.Properties.IPConfigurations { 1013 if ip.Properties.PublicIPAddress != nil && len(ip.Properties.PublicIPAddress.ID) > 0 { 1014 eip, err := self.host.zone.region.GetEip(ip.Properties.PublicIPAddress.ID) 1015 if err != nil { 1016 return nil, errors.Wrapf(err, "GetEip(%s)", ip.Properties.PublicIPAddress.ID) 1017 } 1018 if len(eip.Properties.IPAddress) > 0 { 1019 return eip, nil 1020 } 1021 } 1022 } 1023 } 1024 return nil, nil 1025 } 1026 1027 func (self *SInstance) AssignSecurityGroup(secgroupId string) error { 1028 return self.host.zone.region.SetSecurityGroup(self.ID, secgroupId) 1029 } 1030 1031 func (self *SInstance) SetSecurityGroups(secgroupIds []string) error { 1032 if len(secgroupIds) == 1 { 1033 return self.host.zone.region.SetSecurityGroup(self.ID, secgroupIds[0]) 1034 } 1035 return fmt.Errorf("Unexpect segroup count %d", len(secgroupIds)) 1036 } 1037 1038 func (self *SInstance) GetBillingType() string { 1039 return billing_api.BILLING_TYPE_POSTPAID 1040 } 1041 1042 func (self *SInstance) GetCreatedAt() time.Time { 1043 return self.Properties.TimeCreated 1044 } 1045 1046 func (self *SInstance) GetExpiredAt() time.Time { 1047 return time.Time{} 1048 } 1049 1050 func (self *SInstance) UpdateUserData(userData string) error { 1051 return cloudprovider.ErrNotSupported 1052 } 1053 1054 func (self *SInstance) Renew(bc billing.SBillingCycle) error { 1055 return cloudprovider.ErrNotSupported 1056 } 1057 1058 func (self *SInstance) GetProjectId() string { 1059 return getResourceGroup(self.ID) 1060 } 1061 1062 func (self *SInstance) GetError() error { 1063 if self.Properties.InstanceView != nil { 1064 for _, status := range self.Properties.InstanceView.Statuses { 1065 if status.Code == "ProvisioningState/failed/AllocationFailed" { 1066 return errors.Errorf("%s %s", status.Code, status.Message) 1067 } 1068 } 1069 } 1070 return nil 1071 } 1072 1073 func (self *SRegion) SaveImage(osType, diskId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) { 1074 params := map[string]interface{}{ 1075 "Location": self.Name, 1076 "Name": opts.Name, 1077 "Properties": map[string]interface{}{ 1078 "storageProfile": map[string]interface{}{ 1079 "osDisk": map[string]interface{}{ 1080 "osType": osType, 1081 "managedDisk": map[string]string{ 1082 "id": diskId, 1083 }, 1084 "osState": "Generalized", 1085 }, 1086 }, 1087 }, 1088 "Type": "Microsoft.Compute/images", 1089 } 1090 image := &SImage{storageCache: self.getStoragecache()} 1091 err := self.create("", jsonutils.Marshal(params), image) 1092 if err != nil { 1093 return nil, errors.Wrapf(err, "create image") 1094 } 1095 return image, nil 1096 } 1097 1098 func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) { 1099 if self.Properties.StorageProfile.OsDisk.ManagedDisk == nil { 1100 return nil, fmt.Errorf("invalid os disk for save image") 1101 } 1102 image, err := self.host.zone.region.SaveImage(string(self.GetOsType()), self.Properties.StorageProfile.OsDisk.ManagedDisk.ID, opts) 1103 if err != nil { 1104 return nil, errors.Wrapf(err, "SaveImage") 1105 } 1106 return image, nil 1107 } 1108 1109 func (self *SInstance) SetTags(tags map[string]string, replace bool) error { 1110 if !replace { 1111 for k, v := range self.Tags { 1112 if _, ok := tags[k]; !ok { 1113 tags[k] = v 1114 } 1115 } 1116 } 1117 _, err := self.host.zone.region.client.SetTags(self.ID, tags) 1118 if err != nil { 1119 return errors.Wrapf(err, "self.host.zone.region.client.SetTags(%s,%s)", self.ID, jsonutils.Marshal(tags).String()) 1120 } 1121 return nil 1122 }