yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/google/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 google 16 17 import ( 18 "context" 19 "fmt" 20 "strings" 21 "time" 22 23 "yunion.io/x/jsonutils" 24 "yunion.io/x/log" 25 "yunion.io/x/pkg/errors" 26 "yunion.io/x/pkg/util/fileutils" 27 "yunion.io/x/pkg/utils" 28 29 billing_api "yunion.io/x/cloudmux/pkg/apis/billing" 30 api "yunion.io/x/cloudmux/pkg/apis/compute" 31 "yunion.io/x/cloudmux/pkg/cloudprovider" 32 "yunion.io/x/cloudmux/pkg/multicloud" 33 "yunion.io/x/onecloud/pkg/util/billing" 34 "yunion.io/x/onecloud/pkg/util/cloudinit" 35 "yunion.io/x/onecloud/pkg/util/encode" 36 "yunion.io/x/onecloud/pkg/util/imagetools" 37 "yunion.io/x/onecloud/pkg/util/pinyinutils" 38 ) 39 40 const ( 41 METADATA_SSH_KEYS = "ssh-keys" 42 METADATA_STARTUP_SCRIPT = "startup-script" 43 METADATA_POWER_SHELL = "sysprep-specialize-script-ps1" 44 METADATA_STARTUP_SCRIPT_POWER_SHELL = "windows-startup-script-ps1" 45 ) 46 47 type AccessConfig struct { 48 Type string 49 Name string 50 NatIP string 51 NetworkTier string 52 Kind string 53 } 54 55 type InstanceDisk struct { 56 Type string 57 Mode string 58 Source string 59 DeviceName string 60 Index int 61 Boot bool 62 AutoDelete bool 63 Licenses []string 64 Interface string 65 GuestOsFeatures []GuestOsFeature 66 Kind string 67 } 68 69 type ServiceAccount struct { 70 Email string 71 scopes []string 72 } 73 74 type SInstanceTag struct { 75 Items []string 76 Fingerprint string 77 } 78 79 type SMetadataItem struct { 80 Key string 81 Value string 82 } 83 84 type SMetadata struct { 85 Fingerprint string 86 Items []SMetadataItem 87 } 88 89 type SInstance struct { 90 multicloud.SInstanceBase 91 GoogleTags 92 host *SHost 93 SResourceBase 94 95 osInfo *imagetools.ImageInfo 96 97 CreationTimestamp time.Time 98 Description string 99 Tags SInstanceTag 100 MachineType string 101 Status string 102 Zone string 103 CanIpForward bool 104 NetworkInterfaces []SNetworkInterface 105 Disks []InstanceDisk 106 Metadata SMetadata 107 ServiceAccounts []ServiceAccount 108 Scheduling map[string]interface{} 109 CpuPlatform string 110 LabelFingerprint string 111 StartRestricted bool 112 DeletionProtection bool 113 Kind string 114 115 guestCpus int 116 memoryMb int 117 machineType string 118 } 119 120 func (region *SRegion) GetInstances(zone string, maxResults int, pageToken string) ([]SInstance, error) { 121 instances := []SInstance{} 122 params := map[string]string{} 123 if len(zone) == 0 { 124 return nil, fmt.Errorf("zone params can not be empty") 125 } 126 resource := fmt.Sprintf("zones/%s/instances", zone) 127 return instances, region.List(resource, params, maxResults, pageToken, &instances) 128 } 129 130 func (region *SRegion) GetInstance(id string) (*SInstance, error) { 131 instance := &SInstance{} 132 return instance, region.Get("instances", id, instance) 133 } 134 135 func (instance *SInstance) GetHostname() string { 136 return instance.GetName() 137 } 138 139 func (instance *SInstance) fetchMachineType() error { 140 if instance.guestCpus > 0 || instance.memoryMb > 0 || len(instance.machineType) > 0 { 141 return nil 142 } 143 machinetype := SMachineType{} 144 err := instance.host.zone.region.GetBySelfId(instance.MachineType, &machinetype) 145 if err != nil { 146 return err 147 } 148 instance.guestCpus = machinetype.GuestCpus 149 instance.memoryMb = machinetype.MemoryMb 150 instance.machineType = machinetype.Name 151 return nil 152 } 153 154 func (self *SInstance) Refresh() error { 155 instance, err := self.host.zone.region.GetInstance(self.Id) 156 if err != nil { 157 return err 158 } 159 err = jsonutils.Update(self, instance) 160 if err != nil { 161 return err 162 } 163 instance.Labels = self.Labels 164 return nil 165 } 166 167 //PROVISIONING, STAGING, RUNNING, STOPPING, STOPPED, SUSPENDING, SUSPENDED, and TERMINATED. 168 func (instance *SInstance) GetStatus() string { 169 switch instance.Status { 170 case "PROVISIONING": 171 return api.VM_DEPLOYING 172 case "STAGING": 173 return api.VM_STARTING 174 case "RUNNING": 175 return api.VM_RUNNING 176 case "STOPPING": 177 return api.VM_STOPPING 178 case "STOPPED": 179 return api.VM_READY 180 case "SUSPENDING": 181 return api.VM_SUSPENDING 182 case "SUSPENDED": 183 return api.VM_SUSPEND 184 case "TERMINATED": 185 return api.VM_READY 186 default: 187 return api.VM_UNKNOWN 188 } 189 } 190 191 func (instance *SInstance) GetBillingType() string { 192 return billing_api.BILLING_TYPE_POSTPAID 193 } 194 195 func (instance *SInstance) GetCreatedAt() time.Time { 196 return instance.CreationTimestamp 197 } 198 199 func (instance *SInstance) GetExpiredAt() time.Time { 200 return time.Time{} 201 } 202 203 func (instance *SInstance) GetProjectId() string { 204 return instance.host.zone.region.GetProjectId() 205 } 206 207 func (instance *SInstance) GetIHost() cloudprovider.ICloudHost { 208 return instance.host 209 } 210 211 func (instance *SInstance) GetIHostId() string { 212 return instance.host.GetGlobalId() 213 } 214 215 func (instance *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { 216 idisks := []cloudprovider.ICloudDisk{} 217 for _, disk := range instance.Disks { 218 _disk := &SDisk{} 219 err := instance.host.zone.region.GetBySelfId(disk.Source, _disk) 220 if err != nil { 221 return nil, errors.Wrap(err, "GetDisk") 222 } 223 storage, err := instance.host.zone.region.GetStorage(_disk.Type) 224 if err != nil { 225 return nil, errors.Wrap(err, "GetStorage") 226 } 227 storage.zone = instance.host.zone 228 _disk.storage = storage 229 _disk.autoDelete = disk.AutoDelete 230 _disk.boot = disk.Boot 231 _disk.index = disk.Index 232 idisks = append(idisks, _disk) 233 } 234 return idisks, nil 235 } 236 237 func (instance *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) { 238 nics := []cloudprovider.ICloudNic{} 239 for i := range instance.NetworkInterfaces { 240 instance.NetworkInterfaces[i].instance = instance 241 nics = append(nics, &instance.NetworkInterfaces[i]) 242 } 243 return nics, nil 244 } 245 246 func (instance *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) { 247 for _, networkinterface := range instance.NetworkInterfaces { 248 for _, conf := range networkinterface.AccessConfigs { 249 if len(conf.NatIP) > 0 { 250 eips, err := instance.host.zone.region.GetEips(conf.NatIP, 0, "") 251 if err != nil { 252 return nil, errors.Wrapf(err, "region.GetEip(%s)", conf.NatIP) 253 } 254 if len(eips) == 1 { 255 eips[0].region = instance.host.zone.region 256 return &eips[0], nil 257 } 258 eip := &SAddress{ 259 region: instance.host.zone.region, 260 Status: "IN_USE", 261 Address: conf.NatIP, 262 instanceId: instance.Id, 263 } 264 eip.Id = instance.Id 265 eip.SelfLink = instance.SelfLink 266 return eip, nil 267 } 268 } 269 } 270 return nil, nil 271 } 272 273 func (instance *SInstance) GetVcpuCount() int { 274 instance.fetchMachineType() 275 return instance.guestCpus 276 } 277 278 func (instance *SInstance) GetVmemSizeMB() int { 279 instance.fetchMachineType() 280 return instance.memoryMb 281 } 282 283 func (instance *SInstance) GetBootOrder() string { 284 return "cdn" 285 } 286 287 func (instance *SInstance) GetVga() string { 288 return "std" 289 } 290 291 func (instance *SInstance) GetVdi() string { 292 return "vnc" 293 } 294 295 func (instance *SInstance) GetOsType() cloudprovider.TOsType { 296 return cloudprovider.TOsType(instance.getNormalizedOsInfo().OsType) 297 } 298 299 func (instance *SInstance) getValidLicense() string { 300 for _, disk := range instance.Disks { 301 if disk.Index == 0 { 302 for _, license := range disk.Licenses { 303 if len(license) > 0 { 304 return license 305 } 306 } 307 } 308 } 309 return "" 310 } 311 312 func (instance *SInstance) getNormalizedOsInfo() *imagetools.ImageInfo { 313 if instance.osInfo != nil { 314 return instance.osInfo 315 } 316 osinfo := imagetools.NormalizeImageInfo(instance.getValidLicense(), "", "", "", "") 317 instance.osInfo = &osinfo 318 return instance.osInfo 319 } 320 321 func (instance *SInstance) GetFullOsName() string { 322 return instance.getValidLicense() 323 } 324 325 func (instance *SInstance) GetBios() cloudprovider.TBiosType { 326 return cloudprovider.ToBiosType(instance.getNormalizedOsInfo().OsBios) 327 } 328 329 func (instance *SInstance) GetOsArch() string { 330 return instance.getNormalizedOsInfo().OsArch 331 } 332 333 func (instance *SInstance) GetOsDist() string { 334 return instance.getNormalizedOsInfo().OsDistro 335 } 336 337 func (instance *SInstance) GetOsVersion() string { 338 return instance.getNormalizedOsInfo().OsVersion 339 } 340 341 func (instance *SInstance) GetOsLang() string { 342 return instance.getNormalizedOsInfo().OsLang 343 } 344 345 func (instance *SInstance) GetMachine() string { 346 return "pc" 347 } 348 349 func (instance *SInstance) GetInstanceType() string { 350 instance.fetchMachineType() 351 return instance.machineType 352 } 353 354 func (instance *SInstance) AssignSecurityGroup(id string) error { 355 for _, secgrpType := range []string{SECGROUP_TYPE_TAG, SECGROUP_TYPE_SERVICE_ACCOUNT} { 356 if strings.Contains(id, fmt.Sprintf("/%s/", secgrpType)) { 357 idx := strings.LastIndex(id, "/") + 1 358 if idx <= 0 { 359 return fmt.Errorf("invalid secgroup %s with id %s", secgrpType, id) 360 } 361 secgroup := id[idx:] 362 switch secgrpType { 363 case SECGROUP_TYPE_TAG: 364 tag := strings.ToLower(secgroup) 365 if !utils.IsInStringArray(tag, instance.Tags.Items) { 366 instance.Tags.Items = append(instance.Tags.Items, tag) 367 return instance.host.zone.region.SetResourceTags(instance.SelfLink, instance.Tags) 368 } 369 case SECGROUP_TYPE_SERVICE_ACCOUNT: 370 if len(instance.ServiceAccounts) > 0 { 371 return fmt.Errorf("instance %s has already set serviceAccount %s", instance.Name, instance.ServiceAccounts[0].Email) 372 } 373 return instance.host.zone.region.SetServiceAccount(instance.SelfLink, secgroup) 374 } 375 } 376 } 377 return fmt.Errorf("unknown secgroup type %s", id) 378 } 379 380 func (instance *SInstance) GetSecurityGroupIds() ([]string, error) { 381 secgroupIds := []string{} 382 isecgroups := []cloudprovider.ICloudSecurityGroup{} 383 for _, networkinterface := range instance.NetworkInterfaces { 384 vpc := SVpc{region: instance.host.zone.region} 385 err := instance.host.zone.region.GetBySelfId(networkinterface.Subnetwork, &vpc) 386 if err != nil { 387 return nil, errors.Wrap(err, "GetGlobalNetwork") 388 } 389 _isecgroups, err := vpc.GetISecurityGroups() 390 if err != nil { 391 return nil, errors.Wrap(err, "vpc.GetISecurityGroups") 392 } 393 isecgroups = append(isecgroups, _isecgroups...) 394 for _, isecgroup := range _isecgroups { 395 if len(instance.ServiceAccounts) > 0 && isecgroup.GetName() == instance.ServiceAccounts[0].Email { 396 secgroupIds = append(secgroupIds, isecgroup.GetGlobalId()) 397 } 398 gvpcInfo := strings.Split(vpc.Network, "/") 399 gvpcName := gvpcInfo[len(gvpcInfo)-1] 400 if isecgroup.GetName() == gvpcName && !strings.Contains(isecgroup.GetGlobalId(), fmt.Sprintf("/%s/", SECGROUP_TYPE_TAG)) { 401 secgroupIds = append(secgroupIds, isecgroup.GetGlobalId()) 402 } 403 } 404 } 405 if len(instance.NetworkInterfaces) == 1 { 406 for _, secgroup := range isecgroups { 407 if utils.IsInStringArray(secgroup.GetName(), instance.Tags.Items) && strings.Contains(secgroup.GetGlobalId(), fmt.Sprintf("/%s/", SECGROUP_TYPE_TAG)) { 408 secgroupIds = append(secgroupIds, secgroup.GetGlobalId()) 409 } 410 } 411 } 412 return secgroupIds, nil 413 } 414 415 func (instance *SInstance) SetSecurityGroups(ids []string) error { 416 secgroups := map[string][]string{} 417 for _, id := range ids { 418 for _, secgrpType := range []string{SECGROUP_TYPE_TAG, SECGROUP_TYPE_SERVICE_ACCOUNT} { 419 if strings.Contains(id, fmt.Sprintf("/%s/", secgrpType)) { 420 idx := strings.LastIndex(id, "/") + 1 421 if idx <= 0 { 422 return fmt.Errorf("invalid secgroup %s with id %s", secgrpType, id) 423 } 424 secgroup := id[idx:] 425 if len(secgroup) == 0 { 426 return fmt.Errorf("invalid secgroup %s with id %s", secgrpType, id) 427 } 428 if _, ok := secgroups[secgrpType]; !ok { 429 secgroups[secgrpType] = []string{} 430 } 431 if !utils.IsInStringArray(secgroup, secgroups[secgrpType]) { 432 secgroups[secgrpType] = append(secgroups[secgrpType], secgroup) 433 } 434 } 435 } 436 } 437 if tags, ok := secgroups[SECGROUP_TYPE_TAG]; ok && len(tags) > 0 { 438 for _, tag := range tags { 439 tag = strings.ToLower(tag) 440 if !utils.IsInStringArray(tag, instance.Tags.Items) { 441 instance.Tags.Items = append(instance.Tags.Items, tag) 442 } 443 } 444 err := instance.host.zone.region.SetResourceTags(instance.SelfLink, instance.Tags) 445 if err != nil { 446 return errors.Wrap(err, "SetTags") 447 } 448 } 449 if serviceAccounts, ok := secgroups[SECGROUP_TYPE_SERVICE_ACCOUNT]; ok && len(serviceAccounts) > 0 { 450 if len(serviceAccounts) > 1 { 451 return fmt.Errorf("can not set multi service account for google instance") 452 } 453 return instance.host.zone.region.SetServiceAccount(instance.SelfLink, serviceAccounts[0]) 454 } 455 return nil 456 } 457 458 func (instance *SInstance) GetHypervisor() string { 459 return api.HYPERVISOR_GOOGLE 460 } 461 462 func (instance *SInstance) StartVM(ctx context.Context) error { 463 return instance.host.zone.region.StartInstance(instance.SelfLink) 464 } 465 466 func (instance *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { 467 return instance.host.zone.region.StopInstance(instance.SelfLink) 468 } 469 470 func (instance *SInstance) DeleteVM(ctx context.Context) error { 471 return instance.host.zone.region.Delete(instance.SelfLink) 472 } 473 474 func (instance *SInstance) UpdateVM(ctx context.Context, name string) error { 475 return cloudprovider.ErrNotSupported 476 } 477 478 func (instance *SInstance) UpdateUserData(userData string) error { 479 items := []SMetadataItem{} 480 for _, item := range instance.Metadata.Items { 481 if item.Key != METADATA_STARTUP_SCRIPT && item.Key != METADATA_POWER_SHELL && item.Key != METADATA_STARTUP_SCRIPT_POWER_SHELL { 482 items = append(items, item) 483 } 484 } 485 if len(userData) > 0 { 486 items = append(items, SMetadataItem{Key: METADATA_STARTUP_SCRIPT, Value: userData}) 487 items = append(items, SMetadataItem{Key: METADATA_STARTUP_SCRIPT_POWER_SHELL, Value: userData}) 488 items = append(items, SMetadataItem{Key: METADATA_POWER_SHELL, Value: userData}) 489 } 490 instance.Metadata.Items = items 491 return instance.host.zone.region.SetMetadata(instance.SelfLink, instance.Metadata) 492 } 493 494 func (instance *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) { 495 diskId, err := instance.host.zone.region.RebuildRoot(instance.Id, desc.ImageId, desc.SysSizeGB) 496 if err != nil { 497 return "", errors.Wrap(err, "region.RebuildRoot") 498 } 499 return diskId, instance.DeployVM(ctx, "", desc.Account, desc.Password, desc.PublicKey, false, "") 500 } 501 502 func (instance *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error { 503 conf := cloudinit.SCloudConfig{} 504 user := cloudinit.NewUser(username) 505 if len(password) > 0 { 506 user.Password(password) 507 } 508 if len(publicKey) > 0 { 509 user.SshKey(publicKey) 510 } 511 if len(password) > 0 || len(publicKey) > 0 { 512 conf.MergeUser(user) 513 items := []SMetadataItem{} 514 instance.Refresh() 515 for _, item := range instance.Metadata.Items { 516 if item.Key != METADATA_STARTUP_SCRIPT_POWER_SHELL && item.Key != METADATA_STARTUP_SCRIPT { 517 items = append(items, item) 518 } 519 } 520 items = append(items, SMetadataItem{Key: METADATA_STARTUP_SCRIPT_POWER_SHELL, Value: conf.UserDataPowerShell()}) 521 items = append(items, SMetadataItem{Key: METADATA_STARTUP_SCRIPT, Value: conf.UserDataScript()}) 522 instance.Metadata.Items = items 523 return instance.host.zone.region.SetMetadata(instance.SelfLink, instance.Metadata) 524 } 525 return nil 526 } 527 528 func (instance *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error { 529 return instance.host.zone.region.ChangeInstanceConfig(instance.SelfLink, instance.host.zone.Name, config.InstanceType, config.Cpu, config.MemoryMB) 530 } 531 532 func (instance *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) { 533 return nil, cloudprovider.ErrNotImplemented 534 } 535 536 func (instance *SInstance) AttachDisk(ctx context.Context, diskId string) error { 537 return instance.host.zone.region.AttachDisk(instance.SelfLink, diskId, false) 538 } 539 540 func (instance *SInstance) DetachDisk(ctx context.Context, diskId string) error { 541 _disk, err := instance.host.zone.region.GetDisk(diskId) 542 if err != nil { 543 if errors.Cause(err) == cloudprovider.ErrNotFound { 544 return nil 545 } 546 return errors.Wrapf(err, "GetDisk(%s)", diskId) 547 } 548 for _, disk := range instance.Disks { 549 if disk.Source == _disk.SelfLink { 550 return instance.host.zone.region.DetachDisk(instance.SelfLink, disk.DeviceName) 551 } 552 } 553 return nil 554 } 555 556 func (instance *SInstance) Renew(bc billing.SBillingCycle) error { 557 return cloudprovider.ErrNotSupported 558 } 559 560 func (instance *SInstance) GetError() error { 561 return nil 562 } 563 564 func getDiskInfo(disk string) (cloudprovider.SDiskInfo, error) { 565 result := cloudprovider.SDiskInfo{} 566 diskInfo := strings.Split(disk, ":") 567 for _, d := range diskInfo { 568 if utils.IsInStringArray(d, []string{api.STORAGE_GOOGLE_PD_STANDARD, api.STORAGE_GOOGLE_PD_SSD, api.STORAGE_GOOGLE_LOCAL_SSD, api.STORAGE_GOOGLE_PD_BALANCED}) { 569 result.StorageType = d 570 } else if memSize, err := fileutils.GetSizeMb(d, 'M', 1024); err == nil { 571 result.SizeGB = memSize >> 10 572 } else { 573 result.Name = d 574 } 575 } 576 if len(result.StorageType) == 0 { 577 result.StorageType = api.STORAGE_GOOGLE_PD_STANDARD 578 } 579 580 if result.SizeGB == 0 { 581 return result, fmt.Errorf("Missing disk size") 582 } 583 return result, nil 584 } 585 586 func (region *SRegion) CreateInstance(zone, name, desc, instanceType string, cpu, memoryMb int, networkId string, ipAddr, imageId string, disks []string) (*SInstance, error) { 587 if len(instanceType) == 0 && (cpu == 0 || memoryMb == 0) { 588 return nil, fmt.Errorf("Missing instanceType or cpu &memory info") 589 } 590 if len(disks) == 0 { 591 return nil, fmt.Errorf("Missing disk info") 592 } 593 sysDisk, err := getDiskInfo(disks[0]) 594 if err != nil { 595 return nil, errors.Wrap(err, "getDiskInfo.sys") 596 } 597 dataDisks := []cloudprovider.SDiskInfo{} 598 for _, d := range disks[1:] { 599 dataDisk, err := getDiskInfo(d) 600 if err != nil { 601 return nil, errors.Wrapf(err, "getDiskInfo(%s)", d) 602 } 603 dataDisks = append(dataDisks, dataDisk) 604 } 605 conf := &cloudprovider.SManagedVMCreateConfig{ 606 Name: name, 607 Description: desc, 608 ExternalImageId: imageId, 609 Cpu: cpu, 610 MemoryMB: memoryMb, 611 ExternalNetworkId: networkId, 612 IpAddr: ipAddr, 613 SysDisk: sysDisk, 614 DataDisks: dataDisks, 615 } 616 return region._createVM(zone, conf) 617 } 618 619 func (region *SRegion) getSecgroupByIds(ids []string) (map[string][]string, error) { 620 secgroups := map[string][]string{} 621 for _, id := range ids { 622 for _, secgrpType := range []string{SECGROUP_TYPE_TAG, SECGROUP_TYPE_SERVICE_ACCOUNT} { 623 if strings.Contains(id, fmt.Sprintf("/%s/", secgrpType)) { 624 idx := strings.LastIndex(id, "/") + 1 625 if idx <= 0 { 626 return nil, fmt.Errorf("invalid secgroup %s for %s", id, secgrpType) 627 } 628 secgroup := id[idx:] 629 if len(secgroup) == 0 { 630 return nil, fmt.Errorf("invalid secgroup %s for %s", id, secgrpType) 631 } 632 if _, ok := secgroups[secgrpType]; !ok { 633 secgroups[secgrpType] = []string{} 634 } 635 if !utils.IsInStringArray(secgroup, secgroups[secgrpType]) { 636 secgroups[secgrpType] = append(secgroups[secgrpType], secgroup) 637 } 638 } 639 } 640 } 641 return secgroups, nil 642 } 643 644 func (region *SRegion) _createVM(zone string, desc *cloudprovider.SManagedVMCreateConfig) (*SInstance, error) { 645 vpc, err := region.GetVpc(desc.ExternalNetworkId) 646 if err != nil { 647 return nil, errors.Wrap(err, "region.GetNetwork") 648 } 649 secgroups, err := region.getSecgroupByIds(desc.ExternalSecgroupIds) 650 if err != nil { 651 return nil, errors.Wrap(err, "getSecgroupByIds") 652 } 653 serviceAccounts, ok := secgroups[SECGROUP_TYPE_SERVICE_ACCOUNT] 654 if ok && len(serviceAccounts) > 1 { 655 return nil, fmt.Errorf("Security groups are distributed across multiple service accounts") 656 } 657 if len(desc.InstanceType) == 0 { 658 desc.InstanceType = fmt.Sprintf("custom-%d-%d", desc.Cpu, desc.MemoryMB) 659 } 660 disks := []map[string]interface{}{} 661 if len(desc.SysDisk.Name) == 0 { 662 desc.SysDisk.Name = fmt.Sprintf("vdisk-%s-%d", desc.Name, time.Now().UnixNano()) 663 } 664 nameConv := func(name string) string { 665 name = strings.Replace(name, "_", "-", -1) 666 name = pinyinutils.Text2Pinyin(name) 667 return strings.ToLower(name) 668 } 669 disks = append(disks, map[string]interface{}{ 670 "boot": true, 671 "initializeParams": map[string]interface{}{ 672 "diskName": nameConv(desc.SysDisk.Name), 673 "sourceImage": desc.ExternalImageId, 674 "diskSizeGb": desc.SysDisk.SizeGB, 675 "diskType": fmt.Sprintf("zones/%s/diskTypes/%s", zone, desc.SysDisk.StorageType), 676 }, 677 "autoDelete": true, 678 }) 679 for _, disk := range desc.DataDisks { 680 if len(disk.Name) == 0 { 681 disk.Name = fmt.Sprintf("vdisk-%s-%d", desc.Name, time.Now().UnixNano()) 682 } 683 disks = append(disks, map[string]interface{}{ 684 "boot": false, 685 "initializeParams": map[string]interface{}{ 686 "diskName": nameConv(disk.Name), 687 "diskSizeGb": disk.SizeGB, 688 "diskType": fmt.Sprintf("zones/%s/diskTypes/%s", zone, disk.StorageType), 689 }, 690 "autoDelete": true, 691 }) 692 } 693 networkInterface := map[string]string{ 694 "network": vpc.Network, 695 "subnetwork": vpc.SelfLink, 696 } 697 if len(desc.IpAddr) > 0 { 698 networkInterface["networkIp"] = desc.IpAddr 699 } 700 params := map[string]interface{}{ 701 "name": desc.NameEn, 702 "description": desc.Description, 703 "machineType": fmt.Sprintf("zones/%s/machineTypes/%s", zone, desc.InstanceType), 704 "networkInterfaces": []map[string]string{ 705 networkInterface, 706 }, 707 "disks": disks, 708 } 709 710 labels := map[string]string{} 711 for k, v := range desc.Tags { 712 labels[encode.EncodeGoogleLabel(k)] = encode.EncodeGoogleLabel(v) 713 } 714 715 if len(labels) > 0 { 716 params["labels"] = labels 717 } 718 719 if tags, ok := secgroups[SECGROUP_TYPE_TAG]; ok && len(tags) > 0 { 720 for i := range tags { 721 tags[i] = strings.ToLower(tags[i]) 722 } 723 params["tags"] = map[string][]string{ 724 "items": tags, 725 } 726 } 727 if len(desc.UserData) > 0 { 728 params["metadata"] = map[string]interface{}{ 729 "items": []struct { 730 Key string 731 Value string 732 }{ 733 { 734 Key: METADATA_STARTUP_SCRIPT, 735 Value: desc.UserData, 736 }, 737 { 738 Key: METADATA_POWER_SHELL, 739 Value: desc.UserData, 740 }, 741 }, 742 } 743 } 744 if len(serviceAccounts) > 0 { 745 params["serviceAccounts"] = []struct { 746 Email string 747 Scopes []string 748 }{ 749 { 750 Email: serviceAccounts[0], 751 Scopes: []string{ 752 "https://www.googleapis.com/auth/devstorage.read_only", 753 "https://www.googleapis.com/auth/logging.write", 754 "https://www.googleapis.com/auth/monitoring.write", 755 "https://www.googleapis.com/auth/servicecontrol", 756 "https://www.googleapis.com/auth/service.management.readonly", 757 "https://www.googleapis.com/auth/trace.append", 758 }, 759 }, 760 } 761 } 762 log.Debugf("create google instance params: %s", jsonutils.Marshal(params).String()) 763 instance := &SInstance{} 764 resource := fmt.Sprintf("zones/%s/instances", zone) 765 err = region.Insert(resource, jsonutils.Marshal(params), instance) 766 if err != nil { 767 return nil, err 768 } 769 return instance, nil 770 } 771 772 func (region *SRegion) StartInstance(id string) error { 773 params := map[string]string{} 774 return region.Do(id, "start", nil, jsonutils.Marshal(params)) 775 } 776 777 func (region *SRegion) StopInstance(id string) error { 778 params := map[string]string{} 779 return region.Do(id, "stop", nil, jsonutils.Marshal(params)) 780 } 781 782 func (region *SRegion) ResetInstance(id string) error { 783 params := map[string]string{} 784 return region.Do(id, "reset", nil, jsonutils.Marshal(params)) 785 } 786 787 func (region *SRegion) DetachDisk(instanceId, deviceName string) error { 788 body := map[string]string{} 789 params := map[string]string{"deviceName": deviceName} 790 return region.Do(instanceId, "detachDisk", params, jsonutils.Marshal(body)) 791 } 792 793 func (instance *SInstance) GetSerialOutput(port int) (string, error) { 794 return instance.host.zone.region.GetSerialPortOutput(instance.SelfLink, port) 795 } 796 797 func (region *SRegion) GetSerialPortOutput(id string, port int) (string, error) { 798 _content, content, next := "", "", 0 799 var err error = nil 800 for { 801 _content, next, err = region.getSerialPortOutput(id, port, next) 802 if err != nil { 803 return content, err 804 } 805 content += _content 806 if len(_content) == 0 { 807 break 808 } 809 } 810 return content, nil 811 } 812 813 func (region *SRegion) getSerialPortOutput(id string, port int, start int) (string, int, error) { 814 resource := fmt.Sprintf("%s/serialPort?port=%d&start=%d", id, port, start) 815 result := struct { 816 Contents string 817 Start int 818 Next int 819 }{} 820 err := region.GetBySelfId(resource, &result) 821 if err != nil { 822 return "", result.Next, errors.Wrap(err, "") 823 } 824 return result.Contents, result.Next, nil 825 } 826 827 func (self *SRegion) AttachDisk(instanceId, diskId string, boot bool) error { 828 disk, err := self.GetDisk(diskId) 829 if err != nil { 830 return errors.Wrapf(err, "GetDisk(%s)", diskId) 831 } 832 body := map[string]interface{}{ 833 "source": disk.SelfLink, 834 "boot": boot, 835 } 836 if boot { 837 body["autoDelete"] = true 838 } 839 params := map[string]string{} 840 return self.Do(instanceId, "attachDisk", params, jsonutils.Marshal(body)) 841 } 842 843 func (region *SRegion) ChangeInstanceConfig(id string, zone string, instanceType string, cpu int, memoryMb int) error { 844 if len(instanceType) == 0 { 845 instanceType = fmt.Sprintf("custom-%d-%d", cpu, memoryMb) 846 } 847 params := map[string]string{ 848 "machineType": fmt.Sprintf("zones/%s/machineTypes/%s", zone, instanceType), 849 } 850 return region.Do(id, "setMachineType", nil, jsonutils.Marshal(params)) 851 } 852 853 func (region *SRegion) SetMetadata(id string, metadata SMetadata) error { 854 return region.Do(id, "setMetadata", nil, jsonutils.Marshal(metadata)) 855 } 856 857 func (region *SRegion) SetResourceTags(id string, tags SInstanceTag) error { 858 return region.Do(id, "setTags", nil, jsonutils.Marshal(tags)) 859 } 860 861 func (region *SRegion) SetServiceAccount(id string, email string) error { 862 body := map[string]interface{}{ 863 "email": email, 864 "scopes": []string{ 865 "https://www.googleapis.com/auth/devstorage.read_only", 866 "https://www.googleapis.com/auth/logging.write", 867 "https://www.googleapis.com/auth/monitoring.write", 868 "https://www.googleapis.com/auth/servicecontrol", 869 "https://www.googleapis.com/auth/service.management.readonly", 870 "https://www.googleapis.com/auth/trace.append", 871 }, 872 } 873 return region.Do(id, "setsetServiceAccount", nil, jsonutils.Marshal(body)) 874 } 875 876 func (region *SRegion) RebuildRoot(instanceId string, imageId string, sysDiskSizeGb int) (string, error) { 877 oldDisk, diskType, deviceName := "", api.STORAGE_GOOGLE_PD_STANDARD, "" 878 instance, err := region.GetInstance(instanceId) 879 if err != nil { 880 return "", errors.Wrap(err, "region.GetInstance") 881 } 882 for _, disk := range instance.Disks { 883 if disk.Boot { 884 oldDisk = disk.Source 885 deviceName = disk.DeviceName 886 break 887 } 888 } 889 890 if len(oldDisk) > 0 { 891 disk := &SDisk{} 892 err := region.GetBySelfId(oldDisk, disk) 893 if err != nil { 894 return "", errors.Wrap(err, "region.GetDisk") 895 } 896 diskType = disk.Type 897 if sysDiskSizeGb == 0 { 898 sysDiskSizeGb = disk.SizeGB 899 } 900 } 901 902 zone, err := region.GetZone(instance.Zone) 903 if err != nil { 904 return "", errors.Wrap(err, "region.GetZone") 905 } 906 907 diskName := fmt.Sprintf("vdisk-%s-%d", instance.Name, time.Now().UnixNano()) 908 disk, err := region.CreateDisk(diskName, sysDiskSizeGb, zone.Name, diskType, imageId, "create for replace instance system disk") 909 if err != nil { 910 return "", errors.Wrap(err, "region.CreateDisk.systemDisk") 911 } 912 913 if len(deviceName) > 0 { 914 err = region.DetachDisk(instance.SelfLink, deviceName) 915 if err != nil { 916 defer region.Delete(disk.SelfLink) 917 return "", errors.Wrap(err, "region.DetachDisk") 918 } 919 } 920 921 err = region.AttachDisk(instance.SelfLink, disk.Id, true) 922 if err != nil { 923 if len(oldDisk) > 0 { 924 defer region.AttachDisk(instance.SelfLink, oldDisk, true) 925 } 926 defer region.Delete(disk.SelfLink) 927 return "", errors.Wrap(err, "region.AttachDisk.newSystemDisk") 928 } 929 930 if len(oldDisk) > 0 { 931 defer region.Delete(oldDisk) 932 } 933 return disk.GetGlobalId(), nil 934 } 935 936 func (self *SRegion) SaveImage(diskId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) { 937 params := map[string]interface{}{ 938 "name": opts.Name, 939 "description": opts.Notes, 940 "sourceDisk": diskId, 941 } 942 image := &SImage{} 943 err := self.Insert("global/images", jsonutils.Marshal(params), image) 944 if err != nil { 945 return nil, errors.Wrapf(err, "Insert") 946 } 947 image.storagecache = self.getStoragecache() 948 return image, nil 949 } 950 951 func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) { 952 for i := range self.Disks { 953 if self.Disks[0].Index == 0 { 954 image, err := self.host.zone.region.SaveImage(self.Disks[i].Source, opts) 955 if err != nil { 956 return nil, errors.Wrapf(err, "SaveImage") 957 } 958 return image, nil 959 } 960 } 961 return nil, errors.Wrapf(cloudprovider.ErrNotFound, "no valid system disk found") 962 } 963 964 func (region *SRegion) SetLabels(id string, _labels map[string]string, labelFingerprint string) error { 965 labels := map[string]string{} 966 for k, v := range _labels { 967 labels[encode.EncodeGoogleLabel(k)] = encode.EncodeGoogleLabel(v) 968 } 969 params := map[string]interface{}{ 970 "labels": labels, 971 "labelFingerprint": labelFingerprint, 972 } 973 err := region.Do(id, "setLabels", nil, jsonutils.Marshal(params)) 974 if err != nil { 975 return errors.Wrapf(err, `region.Do(%s, "setLabels", nil, %s)`, id, jsonutils.Marshal(params).String()) 976 } 977 return nil 978 } 979 980 func (self *SInstance) SetTags(tags map[string]string, replace bool) error { 981 if !replace { 982 oldTags, _ := self.GetTags() 983 for k, v := range oldTags { 984 if _, ok := tags[k]; !ok { 985 tags[k] = v 986 } 987 } 988 } 989 err := self.Refresh() 990 if err != nil { 991 return errors.Wrap(err, "self.Refresh()") 992 } 993 err = self.host.zone.region.SetLabels(self.SelfLink, tags, self.LabelFingerprint) 994 if err != nil { 995 return errors.Wrapf(err, ` self.host.zone.region.SsetLabels()`) 996 } 997 return nil 998 }