mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-06-21 21:42:48 +08:00
975 lines
29 KiB
Go
975 lines
29 KiB
Go
// Copyright 2019 Yunion
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package google
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"yunion.io/x/jsonutils"
|
|
"yunion.io/x/log"
|
|
"yunion.io/x/pkg/errors"
|
|
"yunion.io/x/pkg/util/fileutils"
|
|
"yunion.io/x/pkg/utils"
|
|
|
|
billing_api "yunion.io/x/onecloud/pkg/apis/billing"
|
|
api "yunion.io/x/onecloud/pkg/apis/compute"
|
|
"yunion.io/x/onecloud/pkg/cloudprovider"
|
|
"yunion.io/x/onecloud/pkg/multicloud"
|
|
"yunion.io/x/onecloud/pkg/util/billing"
|
|
"yunion.io/x/onecloud/pkg/util/cloudinit"
|
|
"yunion.io/x/onecloud/pkg/util/encode"
|
|
"yunion.io/x/onecloud/pkg/util/imagetools"
|
|
"yunion.io/x/onecloud/pkg/util/pinyinutils"
|
|
)
|
|
|
|
const (
|
|
METADATA_SSH_KEYS = "ssh-keys"
|
|
METADATA_STARTUP_SCRIPT = "startup-script"
|
|
METADATA_POWER_SHELL = "sysprep-specialize-script-ps1"
|
|
METADATA_STARTUP_SCRIPT_POWER_SHELL = "windows-startup-script-ps1"
|
|
)
|
|
|
|
type AccessConfig struct {
|
|
Type string
|
|
Name string
|
|
NatIP string
|
|
NetworkTier string
|
|
Kind string
|
|
}
|
|
|
|
type InstanceDisk struct {
|
|
Type string
|
|
Mode string
|
|
Source string
|
|
DeviceName string
|
|
Index int
|
|
Boot bool
|
|
AutoDelete bool
|
|
Licenses []string
|
|
Interface string
|
|
GuestOsFeatures []GuestOsFeature
|
|
Kind string
|
|
}
|
|
|
|
type ServiceAccount struct {
|
|
Email string
|
|
scopes []string
|
|
}
|
|
|
|
type SInstanceTag struct {
|
|
Items []string
|
|
Fingerprint string
|
|
}
|
|
|
|
type SMetadataItem struct {
|
|
Key string
|
|
Value string
|
|
}
|
|
|
|
type SMetadata struct {
|
|
Fingerprint string
|
|
Items []SMetadataItem
|
|
}
|
|
|
|
type SInstance struct {
|
|
multicloud.SInstanceBase
|
|
multicloud.GoogleTags
|
|
host *SHost
|
|
SResourceBase
|
|
|
|
CreationTimestamp time.Time
|
|
Description string
|
|
Tags SInstanceTag
|
|
MachineType string
|
|
Status string
|
|
Zone string
|
|
CanIpForward bool
|
|
NetworkInterfaces []SNetworkInterface
|
|
Disks []InstanceDisk
|
|
Metadata SMetadata
|
|
ServiceAccounts []ServiceAccount
|
|
Scheduling map[string]interface{}
|
|
CpuPlatform string
|
|
LabelFingerprint string
|
|
StartRestricted bool
|
|
DeletionProtection bool
|
|
Kind string
|
|
|
|
guestCpus int
|
|
memoryMb int
|
|
machineType string
|
|
}
|
|
|
|
func (region *SRegion) GetInstances(zone string, maxResults int, pageToken string) ([]SInstance, error) {
|
|
instances := []SInstance{}
|
|
params := map[string]string{}
|
|
if len(zone) == 0 {
|
|
return nil, fmt.Errorf("zone params can not be empty")
|
|
}
|
|
resource := fmt.Sprintf("zones/%s/instances", zone)
|
|
return instances, region.List(resource, params, maxResults, pageToken, &instances)
|
|
}
|
|
|
|
func (region *SRegion) GetInstance(id string) (*SInstance, error) {
|
|
instance := &SInstance{}
|
|
return instance, region.Get("instances", id, instance)
|
|
}
|
|
|
|
func (instnace *SInstance) IsEmulated() bool {
|
|
return false
|
|
}
|
|
|
|
func (instance *SInstance) fetchMachineType() error {
|
|
if instance.guestCpus > 0 || instance.memoryMb > 0 || len(instance.machineType) > 0 {
|
|
return nil
|
|
}
|
|
machinetype := SMachineType{}
|
|
err := instance.host.zone.region.GetBySelfId(instance.MachineType, &machinetype)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
instance.guestCpus = machinetype.GuestCpus
|
|
instance.memoryMb = machinetype.MemoryMb
|
|
instance.machineType = machinetype.Name
|
|
return nil
|
|
}
|
|
|
|
func (self *SInstance) Refresh() error {
|
|
instance, err := self.host.zone.region.GetInstance(self.Id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = jsonutils.Update(self, instance)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
instance.Labels = self.Labels
|
|
return nil
|
|
}
|
|
|
|
//PROVISIONING, STAGING, RUNNING, STOPPING, STOPPED, SUSPENDING, SUSPENDED, and TERMINATED.
|
|
func (instance *SInstance) GetStatus() string {
|
|
switch instance.Status {
|
|
case "PROVISIONING":
|
|
return api.VM_DEPLOYING
|
|
case "STAGING":
|
|
return api.VM_STARTING
|
|
case "RUNNING":
|
|
return api.VM_RUNNING
|
|
case "STOPPING":
|
|
return api.VM_STOPPING
|
|
case "STOPPED":
|
|
return api.VM_READY
|
|
case "SUSPENDING":
|
|
return api.VM_SUSPENDING
|
|
case "SUSPENDED":
|
|
return api.VM_SUSPEND
|
|
case "TERMINATED":
|
|
return api.VM_READY
|
|
default:
|
|
return api.VM_UNKNOWN
|
|
}
|
|
}
|
|
|
|
func (instance *SInstance) GetBillingType() string {
|
|
return billing_api.BILLING_TYPE_POSTPAID
|
|
}
|
|
|
|
func (instance *SInstance) GetCreatedAt() time.Time {
|
|
return instance.CreationTimestamp
|
|
}
|
|
|
|
func (instance *SInstance) GetExpiredAt() time.Time {
|
|
return time.Time{}
|
|
}
|
|
|
|
func (instance *SInstance) GetProjectId() string {
|
|
return instance.host.zone.region.GetProjectId()
|
|
}
|
|
|
|
func (instance *SInstance) GetIHost() cloudprovider.ICloudHost {
|
|
return instance.host
|
|
}
|
|
|
|
func (instance *SInstance) GetIHostId() string {
|
|
return instance.host.GetGlobalId()
|
|
}
|
|
|
|
func (instance *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
|
|
idisks := []cloudprovider.ICloudDisk{}
|
|
for _, disk := range instance.Disks {
|
|
_disk := &SDisk{}
|
|
err := instance.host.zone.region.GetBySelfId(disk.Source, _disk)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "GetDisk")
|
|
}
|
|
storage, err := instance.host.zone.region.GetStorage(_disk.Type)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "GetStorage")
|
|
}
|
|
storage.zone = instance.host.zone
|
|
_disk.storage = storage
|
|
_disk.autoDelete = disk.AutoDelete
|
|
_disk.boot = disk.Boot
|
|
_disk.index = disk.Index
|
|
idisks = append(idisks, _disk)
|
|
}
|
|
return idisks, nil
|
|
}
|
|
|
|
func (instance *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
|
|
nics := []cloudprovider.ICloudNic{}
|
|
for i := range instance.NetworkInterfaces {
|
|
instance.NetworkInterfaces[i].instance = instance
|
|
nics = append(nics, &instance.NetworkInterfaces[i])
|
|
}
|
|
return nics, nil
|
|
}
|
|
|
|
func (instance *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
|
|
for _, networkinterface := range instance.NetworkInterfaces {
|
|
for _, conf := range networkinterface.AccessConfigs {
|
|
if len(conf.NatIP) > 0 {
|
|
eips, err := instance.host.zone.region.GetEips(conf.NatIP, 0, "")
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "region.GetEip(%s)", conf.NatIP)
|
|
}
|
|
if len(eips) == 1 {
|
|
eips[0].region = instance.host.zone.region
|
|
return &eips[0], nil
|
|
}
|
|
eip := &SAddress{
|
|
region: instance.host.zone.region,
|
|
Status: "IN_USE",
|
|
Address: conf.NatIP,
|
|
instanceId: instance.Id,
|
|
}
|
|
eip.Id = instance.Id
|
|
eip.SelfLink = instance.SelfLink
|
|
return eip, nil
|
|
}
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (instance *SInstance) GetVcpuCount() int {
|
|
instance.fetchMachineType()
|
|
return instance.guestCpus
|
|
}
|
|
|
|
func (instance *SInstance) GetVmemSizeMB() int {
|
|
instance.fetchMachineType()
|
|
return instance.memoryMb
|
|
}
|
|
|
|
func (instance *SInstance) GetBootOrder() string {
|
|
return "cdn"
|
|
}
|
|
|
|
func (instance *SInstance) GetVga() string {
|
|
return "std"
|
|
}
|
|
|
|
func (instance *SInstance) GetVdi() string {
|
|
return "vnc"
|
|
}
|
|
|
|
func (instance *SInstance) GetOsType() cloudprovider.TOsType {
|
|
for _, disk := range instance.Disks {
|
|
if disk.Index == 0 {
|
|
for _, license := range disk.Licenses {
|
|
if strings.Contains(strings.ToLower(license), "windows") {
|
|
return cloudprovider.OsTypeWindows
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return cloudprovider.OsTypeLinux
|
|
}
|
|
|
|
func (instance *SInstance) GetOSName() string {
|
|
for _, disk := range instance.Disks {
|
|
if disk.Index == 0 {
|
|
for _, license := range disk.Licenses {
|
|
return imagetools.NormalizeImageInfo(license, "", "", "", "").OsDistro
|
|
}
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (instance *SInstance) GetBios() string {
|
|
return "BIOS"
|
|
}
|
|
|
|
func (instance *SInstance) GetMachine() string {
|
|
return "pc"
|
|
}
|
|
|
|
func (instance *SInstance) GetInstanceType() string {
|
|
instance.fetchMachineType()
|
|
return instance.machineType
|
|
}
|
|
|
|
func (instance *SInstance) AssignSecurityGroup(id string) error {
|
|
for _, secgrpType := range []string{SECGROUP_TYPE_TAG, SECGROUP_TYPE_SERVICE_ACCOUNT} {
|
|
if strings.Contains(id, fmt.Sprintf("/%s/", secgrpType)) {
|
|
idx := strings.LastIndex(id, "/") + 1
|
|
if idx <= 0 {
|
|
return fmt.Errorf("invalid secgroup %s with id %s", secgrpType, id)
|
|
}
|
|
secgroup := id[idx:]
|
|
switch secgrpType {
|
|
case SECGROUP_TYPE_TAG:
|
|
tag := strings.ToLower(secgroup)
|
|
if !utils.IsInStringArray(tag, instance.Tags.Items) {
|
|
instance.Tags.Items = append(instance.Tags.Items, tag)
|
|
return instance.host.zone.region.SetResourceTags(instance.SelfLink, instance.Tags)
|
|
}
|
|
case SECGROUP_TYPE_SERVICE_ACCOUNT:
|
|
if len(instance.ServiceAccounts) > 0 {
|
|
return fmt.Errorf("instance %s has already set serviceAccount %s", instance.Name, instance.ServiceAccounts[0].Email)
|
|
}
|
|
return instance.host.zone.region.SetServiceAccount(instance.SelfLink, secgroup)
|
|
}
|
|
}
|
|
}
|
|
return fmt.Errorf("unknown secgroup type %s", id)
|
|
}
|
|
|
|
func (instance *SInstance) GetSecurityGroupIds() ([]string, error) {
|
|
secgroupIds := []string{}
|
|
isecgroups := []cloudprovider.ICloudSecurityGroup{}
|
|
for _, networkinterface := range instance.NetworkInterfaces {
|
|
vpc := SVpc{region: instance.host.zone.region}
|
|
err := instance.host.zone.region.GetBySelfId(networkinterface.Subnetwork, &vpc)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "GetGlobalNetwork")
|
|
}
|
|
_isecgroups, err := vpc.GetISecurityGroups()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "vpc.GetISecurityGroups")
|
|
}
|
|
isecgroups = append(isecgroups, _isecgroups...)
|
|
for _, isecgroup := range _isecgroups {
|
|
if len(instance.ServiceAccounts) > 0 && isecgroup.GetName() == instance.ServiceAccounts[0].Email {
|
|
secgroupIds = append(secgroupIds, isecgroup.GetGlobalId())
|
|
}
|
|
gvpcInfo := strings.Split(vpc.Network, "/")
|
|
gvpcName := gvpcInfo[len(gvpcInfo)-1]
|
|
if isecgroup.GetName() == gvpcName && !strings.Contains(isecgroup.GetGlobalId(), fmt.Sprintf("/%s/", SECGROUP_TYPE_TAG)) {
|
|
secgroupIds = append(secgroupIds, isecgroup.GetGlobalId())
|
|
}
|
|
}
|
|
}
|
|
if len(instance.NetworkInterfaces) == 1 {
|
|
for _, secgroup := range isecgroups {
|
|
if utils.IsInStringArray(secgroup.GetName(), instance.Tags.Items) && strings.Contains(secgroup.GetGlobalId(), fmt.Sprintf("/%s/", SECGROUP_TYPE_TAG)) {
|
|
secgroupIds = append(secgroupIds, secgroup.GetGlobalId())
|
|
}
|
|
}
|
|
}
|
|
return secgroupIds, nil
|
|
}
|
|
|
|
func (instance *SInstance) SetSecurityGroups(ids []string) error {
|
|
secgroups := map[string][]string{}
|
|
for _, id := range ids {
|
|
for _, secgrpType := range []string{SECGROUP_TYPE_TAG, SECGROUP_TYPE_SERVICE_ACCOUNT} {
|
|
if strings.Contains(id, fmt.Sprintf("/%s/", secgrpType)) {
|
|
idx := strings.LastIndex(id, "/") + 1
|
|
if idx <= 0 {
|
|
return fmt.Errorf("invalid secgroup %s with id %s", secgrpType, id)
|
|
}
|
|
secgroup := id[idx:]
|
|
if len(secgroup) == 0 {
|
|
return fmt.Errorf("invalid secgroup %s with id %s", secgrpType, id)
|
|
}
|
|
if _, ok := secgroups[secgrpType]; !ok {
|
|
secgroups[secgrpType] = []string{}
|
|
}
|
|
if !utils.IsInStringArray(secgroup, secgroups[secgrpType]) {
|
|
secgroups[secgrpType] = append(secgroups[secgrpType], secgroup)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if tags, ok := secgroups[SECGROUP_TYPE_TAG]; ok && len(tags) > 0 {
|
|
for _, tag := range tags {
|
|
tag = strings.ToLower(tag)
|
|
if !utils.IsInStringArray(tag, instance.Tags.Items) {
|
|
instance.Tags.Items = append(instance.Tags.Items, tag)
|
|
}
|
|
}
|
|
err := instance.host.zone.region.SetResourceTags(instance.SelfLink, instance.Tags)
|
|
if err != nil {
|
|
return errors.Wrap(err, "SetTags")
|
|
}
|
|
}
|
|
if serviceAccounts, ok := secgroups[SECGROUP_TYPE_SERVICE_ACCOUNT]; ok && len(serviceAccounts) > 0 {
|
|
if len(serviceAccounts) > 1 {
|
|
return fmt.Errorf("can not set multi service account for google instance")
|
|
}
|
|
return instance.host.zone.region.SetServiceAccount(instance.SelfLink, serviceAccounts[0])
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (instance *SInstance) GetHypervisor() string {
|
|
return api.HYPERVISOR_GOOGLE
|
|
}
|
|
|
|
func (instance *SInstance) StartVM(ctx context.Context) error {
|
|
return instance.host.zone.region.StartInstance(instance.SelfLink)
|
|
}
|
|
|
|
func (instance *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
|
|
return instance.host.zone.region.StopInstance(instance.SelfLink)
|
|
}
|
|
|
|
func (instance *SInstance) DeleteVM(ctx context.Context) error {
|
|
return instance.host.zone.region.Delete(instance.SelfLink)
|
|
}
|
|
|
|
func (instance *SInstance) UpdateVM(ctx context.Context, name string) error {
|
|
return cloudprovider.ErrNotSupported
|
|
}
|
|
|
|
func (instance *SInstance) UpdateUserData(userData string) error {
|
|
items := []SMetadataItem{}
|
|
for _, item := range instance.Metadata.Items {
|
|
if item.Key != METADATA_STARTUP_SCRIPT && item.Key != METADATA_POWER_SHELL && item.Key != METADATA_STARTUP_SCRIPT_POWER_SHELL {
|
|
items = append(items, item)
|
|
}
|
|
}
|
|
if len(userData) > 0 {
|
|
items = append(items, SMetadataItem{Key: METADATA_STARTUP_SCRIPT, Value: userData})
|
|
items = append(items, SMetadataItem{Key: METADATA_STARTUP_SCRIPT_POWER_SHELL, Value: userData})
|
|
items = append(items, SMetadataItem{Key: METADATA_POWER_SHELL, Value: userData})
|
|
}
|
|
instance.Metadata.Items = items
|
|
return instance.host.zone.region.SetMetadata(instance.SelfLink, instance.Metadata)
|
|
}
|
|
|
|
func (instance *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
|
|
diskId, err := instance.host.zone.region.RebuildRoot(instance.Id, desc.ImageId, desc.SysSizeGB)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "region.RebuildRoot")
|
|
}
|
|
return diskId, instance.DeployVM(ctx, "", desc.Account, desc.Password, desc.PublicKey, false, "")
|
|
}
|
|
|
|
func (instance *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error {
|
|
conf := cloudinit.SCloudConfig{}
|
|
user := cloudinit.NewUser(username)
|
|
if len(password) > 0 {
|
|
user.Password(password)
|
|
}
|
|
if len(publicKey) > 0 {
|
|
user.SshKey(publicKey)
|
|
}
|
|
if len(password) > 0 || len(publicKey) > 0 {
|
|
conf.MergeUser(user)
|
|
items := []SMetadataItem{}
|
|
instance.Refresh()
|
|
for _, item := range instance.Metadata.Items {
|
|
if item.Key != METADATA_STARTUP_SCRIPT_POWER_SHELL && item.Key != METADATA_STARTUP_SCRIPT {
|
|
items = append(items, item)
|
|
}
|
|
}
|
|
items = append(items, SMetadataItem{Key: METADATA_STARTUP_SCRIPT_POWER_SHELL, Value: conf.UserDataPowerShell()})
|
|
items = append(items, SMetadataItem{Key: METADATA_STARTUP_SCRIPT, Value: conf.UserDataScript()})
|
|
instance.Metadata.Items = items
|
|
return instance.host.zone.region.SetMetadata(instance.SelfLink, instance.Metadata)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (instance *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error {
|
|
return instance.host.zone.region.ChangeInstanceConfig(instance.SelfLink, instance.host.zone.Name, config.InstanceType, config.Cpu, config.MemoryMB)
|
|
}
|
|
|
|
func (instance *SInstance) GetVNCInfo() (jsonutils.JSONObject, error) {
|
|
return nil, cloudprovider.ErrNotImplemented
|
|
}
|
|
|
|
func (instance *SInstance) AttachDisk(ctx context.Context, diskId string) error {
|
|
return instance.host.zone.region.AttachDisk(instance.SelfLink, diskId, false)
|
|
}
|
|
|
|
func (instance *SInstance) DetachDisk(ctx context.Context, diskId string) error {
|
|
_disk, err := instance.host.zone.region.GetDisk(diskId)
|
|
if err != nil {
|
|
if errors.Cause(err) == cloudprovider.ErrNotFound {
|
|
return nil
|
|
}
|
|
return errors.Wrapf(err, "GetDisk(%s)", diskId)
|
|
}
|
|
for _, disk := range instance.Disks {
|
|
if disk.Source == _disk.SelfLink {
|
|
return instance.host.zone.region.DetachDisk(instance.SelfLink, disk.DeviceName)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (instance *SInstance) Renew(bc billing.SBillingCycle) error {
|
|
return cloudprovider.ErrNotSupported
|
|
}
|
|
|
|
func (instance *SInstance) GetError() error {
|
|
return nil
|
|
}
|
|
|
|
func getDiskInfo(disk string) (cloudprovider.SDiskInfo, error) {
|
|
result := cloudprovider.SDiskInfo{}
|
|
diskInfo := strings.Split(disk, ":")
|
|
for _, d := range diskInfo {
|
|
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}) {
|
|
result.StorageType = d
|
|
} else if memSize, err := fileutils.GetSizeMb(d, 'M', 1024); err == nil {
|
|
result.SizeGB = memSize >> 10
|
|
} else {
|
|
result.Name = d
|
|
}
|
|
}
|
|
if len(result.StorageType) == 0 {
|
|
result.StorageType = api.STORAGE_GOOGLE_PD_STANDARD
|
|
}
|
|
|
|
if result.SizeGB == 0 {
|
|
return result, fmt.Errorf("Missing disk size")
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (region *SRegion) CreateInstance(zone, name, desc, instanceType string, cpu, memoryMb int, networkId string, ipAddr, imageId string, disks []string) (*SInstance, error) {
|
|
if len(instanceType) == 0 && (cpu == 0 || memoryMb == 0) {
|
|
return nil, fmt.Errorf("Missing instanceType or cpu &memory info")
|
|
}
|
|
if len(disks) == 0 {
|
|
return nil, fmt.Errorf("Missing disk info")
|
|
}
|
|
sysDisk, err := getDiskInfo(disks[0])
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "getDiskInfo.sys")
|
|
}
|
|
dataDisks := []cloudprovider.SDiskInfo{}
|
|
for _, d := range disks[1:] {
|
|
dataDisk, err := getDiskInfo(d)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "getDiskInfo(%s)", d)
|
|
}
|
|
dataDisks = append(dataDisks, dataDisk)
|
|
}
|
|
conf := &cloudprovider.SManagedVMCreateConfig{
|
|
Name: name,
|
|
Description: desc,
|
|
ExternalImageId: imageId,
|
|
Cpu: cpu,
|
|
MemoryMB: memoryMb,
|
|
ExternalNetworkId: networkId,
|
|
IpAddr: ipAddr,
|
|
SysDisk: sysDisk,
|
|
DataDisks: dataDisks,
|
|
}
|
|
return region._createVM(zone, conf)
|
|
}
|
|
|
|
func (region *SRegion) getSecgroupByIds(ids []string) (map[string][]string, error) {
|
|
secgroups := map[string][]string{}
|
|
for _, id := range ids {
|
|
for _, secgrpType := range []string{SECGROUP_TYPE_TAG, SECGROUP_TYPE_SERVICE_ACCOUNT} {
|
|
if strings.Contains(id, fmt.Sprintf("/%s/", secgrpType)) {
|
|
idx := strings.LastIndex(id, "/") + 1
|
|
if idx <= 0 {
|
|
return nil, fmt.Errorf("invalid secgroup %s for %s", id, secgrpType)
|
|
}
|
|
secgroup := id[idx:]
|
|
if len(secgroup) == 0 {
|
|
return nil, fmt.Errorf("invalid secgroup %s for %s", id, secgrpType)
|
|
}
|
|
if _, ok := secgroups[secgrpType]; !ok {
|
|
secgroups[secgrpType] = []string{}
|
|
}
|
|
if !utils.IsInStringArray(secgroup, secgroups[secgrpType]) {
|
|
secgroups[secgrpType] = append(secgroups[secgrpType], secgroup)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return secgroups, nil
|
|
}
|
|
|
|
func (region *SRegion) _createVM(zone string, desc *cloudprovider.SManagedVMCreateConfig) (*SInstance, error) {
|
|
vpc, err := region.GetVpc(desc.ExternalNetworkId)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "region.GetNetwork")
|
|
}
|
|
secgroups, err := region.getSecgroupByIds(desc.ExternalSecgroupIds)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "getSecgroupByIds")
|
|
}
|
|
serviceAccounts, ok := secgroups[SECGROUP_TYPE_SERVICE_ACCOUNT]
|
|
if ok && len(serviceAccounts) > 1 {
|
|
return nil, fmt.Errorf("Security groups are distributed across multiple service accounts")
|
|
}
|
|
if len(desc.InstanceType) == 0 {
|
|
desc.InstanceType = fmt.Sprintf("custom-%d-%d", desc.Cpu, desc.MemoryMB)
|
|
}
|
|
disks := []map[string]interface{}{}
|
|
if len(desc.SysDisk.Name) == 0 {
|
|
desc.SysDisk.Name = fmt.Sprintf("vdisk-%s-%d", desc.Name, time.Now().UnixNano())
|
|
}
|
|
nameConv := func(name string) string {
|
|
name = strings.Replace(name, "_", "-", -1)
|
|
name = pinyinutils.Text2Pinyin(name)
|
|
return strings.ToLower(name)
|
|
}
|
|
disks = append(disks, map[string]interface{}{
|
|
"boot": true,
|
|
"initializeParams": map[string]interface{}{
|
|
"diskName": nameConv(desc.SysDisk.Name),
|
|
"sourceImage": desc.ExternalImageId,
|
|
"diskSizeGb": desc.SysDisk.SizeGB,
|
|
"diskType": fmt.Sprintf("zones/%s/diskTypes/%s", zone, desc.SysDisk.StorageType),
|
|
},
|
|
"autoDelete": true,
|
|
})
|
|
for _, disk := range desc.DataDisks {
|
|
if len(disk.Name) == 0 {
|
|
disk.Name = fmt.Sprintf("vdisk-%s-%d", desc.Name, time.Now().UnixNano())
|
|
}
|
|
disks = append(disks, map[string]interface{}{
|
|
"boot": false,
|
|
"initializeParams": map[string]interface{}{
|
|
"diskName": nameConv(disk.Name),
|
|
"diskSizeGb": disk.SizeGB,
|
|
"diskType": fmt.Sprintf("zones/%s/diskTypes/%s", zone, disk.StorageType),
|
|
},
|
|
"autoDelete": true,
|
|
})
|
|
}
|
|
networkInterface := map[string]string{
|
|
"network": vpc.Network,
|
|
"subnetwork": vpc.SelfLink,
|
|
}
|
|
if len(desc.IpAddr) > 0 {
|
|
networkInterface["networkIp"] = desc.IpAddr
|
|
}
|
|
params := map[string]interface{}{
|
|
"name": desc.Name,
|
|
"description": desc.Description,
|
|
"machineType": fmt.Sprintf("zones/%s/machineTypes/%s", zone, desc.InstanceType),
|
|
"networkInterfaces": []map[string]string{
|
|
networkInterface,
|
|
},
|
|
"disks": disks,
|
|
}
|
|
|
|
labels := map[string]string{}
|
|
for k, v := range desc.Tags {
|
|
labels[encode.EncodeGoogleLabel(k)] = encode.EncodeGoogleLabel(v)
|
|
}
|
|
|
|
if len(labels) > 0 {
|
|
params["labels"] = labels
|
|
}
|
|
|
|
if tags, ok := secgroups[SECGROUP_TYPE_TAG]; ok && len(tags) > 0 {
|
|
for i := range tags {
|
|
tags[i] = strings.ToLower(tags[i])
|
|
}
|
|
params["tags"] = map[string][]string{
|
|
"items": tags,
|
|
}
|
|
}
|
|
if len(desc.UserData) > 0 {
|
|
params["metadata"] = map[string]interface{}{
|
|
"items": []struct {
|
|
Key string
|
|
Value string
|
|
}{
|
|
{
|
|
Key: METADATA_STARTUP_SCRIPT,
|
|
Value: desc.UserData,
|
|
},
|
|
{
|
|
Key: METADATA_POWER_SHELL,
|
|
Value: desc.UserData,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
if len(serviceAccounts) > 0 {
|
|
params["serviceAccounts"] = []struct {
|
|
Email string
|
|
Scopes []string
|
|
}{
|
|
{
|
|
Email: serviceAccounts[0],
|
|
Scopes: []string{
|
|
"https://www.googleapis.com/auth/devstorage.read_only",
|
|
"https://www.googleapis.com/auth/logging.write",
|
|
"https://www.googleapis.com/auth/monitoring.write",
|
|
"https://www.googleapis.com/auth/servicecontrol",
|
|
"https://www.googleapis.com/auth/service.management.readonly",
|
|
"https://www.googleapis.com/auth/trace.append",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
log.Debugf("create google instance params: %s", jsonutils.Marshal(params).String())
|
|
instance := &SInstance{}
|
|
resource := fmt.Sprintf("zones/%s/instances", zone)
|
|
err = region.Insert(resource, jsonutils.Marshal(params), instance)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return instance, nil
|
|
}
|
|
|
|
func (region *SRegion) StartInstance(id string) error {
|
|
params := map[string]string{}
|
|
return region.Do(id, "start", nil, jsonutils.Marshal(params))
|
|
}
|
|
|
|
func (region *SRegion) StopInstance(id string) error {
|
|
params := map[string]string{}
|
|
return region.Do(id, "stop", nil, jsonutils.Marshal(params))
|
|
}
|
|
|
|
func (region *SRegion) ResetInstance(id string) error {
|
|
params := map[string]string{}
|
|
return region.Do(id, "reset", nil, jsonutils.Marshal(params))
|
|
}
|
|
|
|
func (region *SRegion) DetachDisk(instanceId, deviceName string) error {
|
|
body := map[string]string{}
|
|
params := map[string]string{"deviceName": deviceName}
|
|
return region.Do(instanceId, "detachDisk", params, jsonutils.Marshal(body))
|
|
}
|
|
|
|
func (instance *SInstance) GetSerialOutput(port int) (string, error) {
|
|
return instance.host.zone.region.GetSerialPortOutput(instance.SelfLink, port)
|
|
}
|
|
|
|
func (region *SRegion) GetSerialPortOutput(id string, port int) (string, error) {
|
|
_content, content, next := "", "", 0
|
|
var err error = nil
|
|
for {
|
|
_content, next, err = region.getSerialPortOutput(id, port, next)
|
|
if err != nil {
|
|
return content, err
|
|
}
|
|
content += _content
|
|
if len(_content) == 0 {
|
|
break
|
|
}
|
|
}
|
|
return content, nil
|
|
}
|
|
|
|
func (region *SRegion) getSerialPortOutput(id string, port int, start int) (string, int, error) {
|
|
resource := fmt.Sprintf("%s/serialPort?port=%d&start=%d", id, port, start)
|
|
result := struct {
|
|
Contents string
|
|
Start int
|
|
Next int
|
|
}{}
|
|
err := region.GetBySelfId(resource, &result)
|
|
if err != nil {
|
|
return "", result.Next, errors.Wrap(err, "")
|
|
}
|
|
return result.Contents, result.Next, nil
|
|
}
|
|
|
|
func (self *SRegion) AttachDisk(instanceId, diskId string, boot bool) error {
|
|
disk, err := self.GetDisk(diskId)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "GetDisk(%s)", diskId)
|
|
}
|
|
body := map[string]interface{}{
|
|
"source": disk.SelfLink,
|
|
"boot": boot,
|
|
}
|
|
if boot {
|
|
body["autoDelete"] = true
|
|
}
|
|
params := map[string]string{}
|
|
return self.Do(instanceId, "attachDisk", params, jsonutils.Marshal(body))
|
|
}
|
|
|
|
func (region *SRegion) ChangeInstanceConfig(id string, zone string, instanceType string, cpu int, memoryMb int) error {
|
|
if len(instanceType) == 0 {
|
|
instanceType = fmt.Sprintf("custom-%d-%d", cpu, memoryMb)
|
|
}
|
|
params := map[string]string{
|
|
"machineType": fmt.Sprintf("zones/%s/machineTypes/%s", zone, instanceType),
|
|
}
|
|
return region.Do(id, "setMachineType", nil, jsonutils.Marshal(params))
|
|
}
|
|
|
|
func (region *SRegion) SetMetadata(id string, metadata SMetadata) error {
|
|
return region.Do(id, "setMetadata", nil, jsonutils.Marshal(metadata))
|
|
}
|
|
|
|
func (region *SRegion) SetResourceTags(id string, tags SInstanceTag) error {
|
|
return region.Do(id, "setTags", nil, jsonutils.Marshal(tags))
|
|
}
|
|
|
|
func (region *SRegion) SetServiceAccount(id string, email string) error {
|
|
body := map[string]interface{}{
|
|
"email": email,
|
|
"scopes": []string{
|
|
"https://www.googleapis.com/auth/devstorage.read_only",
|
|
"https://www.googleapis.com/auth/logging.write",
|
|
"https://www.googleapis.com/auth/monitoring.write",
|
|
"https://www.googleapis.com/auth/servicecontrol",
|
|
"https://www.googleapis.com/auth/service.management.readonly",
|
|
"https://www.googleapis.com/auth/trace.append",
|
|
},
|
|
}
|
|
return region.Do(id, "setsetServiceAccount", nil, jsonutils.Marshal(body))
|
|
}
|
|
|
|
func (region *SRegion) RebuildRoot(instanceId string, imageId string, sysDiskSizeGb int) (string, error) {
|
|
oldDisk, diskType, deviceName := "", api.STORAGE_GOOGLE_PD_STANDARD, ""
|
|
instance, err := region.GetInstance(instanceId)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "region.GetInstance")
|
|
}
|
|
for _, disk := range instance.Disks {
|
|
if disk.Boot {
|
|
oldDisk = disk.Source
|
|
deviceName = disk.DeviceName
|
|
break
|
|
}
|
|
}
|
|
|
|
if len(oldDisk) > 0 {
|
|
disk := &SDisk{}
|
|
err := region.GetBySelfId(oldDisk, disk)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "region.GetDisk")
|
|
}
|
|
diskType = disk.Type
|
|
if sysDiskSizeGb == 0 {
|
|
sysDiskSizeGb = disk.SizeGB
|
|
}
|
|
}
|
|
|
|
zone, err := region.GetZone(instance.Zone)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "region.GetZone")
|
|
}
|
|
|
|
diskName := fmt.Sprintf("vdisk-%s-%d", instance.Name, time.Now().UnixNano())
|
|
disk, err := region.CreateDisk(diskName, sysDiskSizeGb, zone.Name, diskType, imageId, "create for replace instance system disk")
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "region.CreateDisk.systemDisk")
|
|
}
|
|
|
|
if len(deviceName) > 0 {
|
|
err = region.DetachDisk(instance.SelfLink, deviceName)
|
|
if err != nil {
|
|
defer region.Delete(disk.SelfLink)
|
|
return "", errors.Wrap(err, "region.DetachDisk")
|
|
}
|
|
}
|
|
|
|
err = region.AttachDisk(instance.SelfLink, disk.Id, true)
|
|
if err != nil {
|
|
if len(oldDisk) > 0 {
|
|
defer region.AttachDisk(instance.SelfLink, oldDisk, true)
|
|
}
|
|
defer region.Delete(disk.SelfLink)
|
|
return "", errors.Wrap(err, "region.AttachDisk.newSystemDisk")
|
|
}
|
|
|
|
if len(oldDisk) > 0 {
|
|
defer region.Delete(oldDisk)
|
|
}
|
|
return disk.GetGlobalId(), nil
|
|
}
|
|
|
|
func (self *SRegion) SaveImage(diskId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) {
|
|
params := map[string]interface{}{
|
|
"name": opts.Name,
|
|
"description": opts.Notes,
|
|
"sourceDisk": diskId,
|
|
}
|
|
image := &SImage{}
|
|
err := self.Insert("global/images", jsonutils.Marshal(params), image)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "Insert")
|
|
}
|
|
image.storagecache = self.getStoragecache()
|
|
return image, nil
|
|
}
|
|
|
|
func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) {
|
|
for i := range self.Disks {
|
|
if self.Disks[0].Index == 0 {
|
|
image, err := self.host.zone.region.SaveImage(self.Disks[i].Source, opts)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "SaveImage")
|
|
}
|
|
return image, nil
|
|
}
|
|
}
|
|
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "no valid system disk found")
|
|
}
|
|
|
|
func (region *SRegion) SetLabels(id string, _labels map[string]string, labelFingerprint string) error {
|
|
labels := map[string]string{}
|
|
for k, v := range _labels {
|
|
labels[encode.EncodeGoogleLabel(k)] = encode.EncodeGoogleLabel(v)
|
|
}
|
|
params := map[string]interface{}{
|
|
"labels": labels,
|
|
"labelFingerprint": labelFingerprint,
|
|
}
|
|
err := region.Do(id, "setLabels", nil, jsonutils.Marshal(params))
|
|
if err != nil {
|
|
return errors.Wrapf(err, `region.Do(%s, "setLabels", nil, %s)`, id, jsonutils.Marshal(params).String())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (self *SInstance) SetTags(tags map[string]string, replace bool) error {
|
|
if !replace {
|
|
oldTags, _ := self.GetTags()
|
|
for k, v := range oldTags {
|
|
if _, ok := tags[k]; !ok {
|
|
tags[k] = v
|
|
}
|
|
}
|
|
}
|
|
err := self.Refresh()
|
|
if err != nil {
|
|
return errors.Wrap(err, "self.Refresh()")
|
|
}
|
|
err = self.host.zone.region.SetLabels(self.SelfLink, tags, self.LabelFingerprint)
|
|
if err != nil {
|
|
return errors.Wrapf(err, ` self.host.zone.region.SsetLabels()`)
|
|
}
|
|
return nil
|
|
}
|