diff --git a/cmd/climc/shell/images.go b/cmd/climc/shell/images.go index a892602625..22f11b1ed0 100644 --- a/cmd/climc/shell/images.go +++ b/cmd/climc/shell/images.go @@ -50,7 +50,7 @@ type ImageOptionalOptions struct { OsLang string `help:"OS Language" choices:"zh_CN|en_US"` Preference int64 `help:"Disk preferences"` Notes string `help:"Notes about the image"` - Hypervisor []string `help:"Prefer hypervisor type" choices:"kvm|esxi|baremetal|container|openstack"` + Hypervisor []string `help:"Prefer hypervisor type" choices:"kvm|esxi|baremetal|container|openstack|ctyun"` DiskDriver string `help:"Perfer disk driver" choices:"virtio|scsi|pvscsi|ide|sata"` NetDriver string `help:"Preferred network driver" choices:"virtio|e1000|vmxnet3"` } diff --git a/go.sum b/go.sum index 7e13bcb8f7..e93ee1fc4f 100644 --- a/go.sum +++ b/go.sum @@ -679,8 +679,6 @@ yunion.io/x/pkg v0.0.0-20200103043034-27c6f82160fa h1:+7zYi8MhaOW/53/7FOERnhQqAU yunion.io/x/pkg v0.0.0-20200103043034-27c6f82160fa/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E= yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e h1:v+EzIadodSwkdZ/7bremd7J8J50Cise/HCylsOJngmo= yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e/go.mod h1:0iFKpOs1y4lbCxeOmq3Xx/0AcQoewVPwj62eRluioEo= -yunion.io/x/sqlchemy v0.0.0-20191226074733-6eb73845bfb7 h1:nK047S9fIjIBkB9Yyo2e6itUA7TpmFsd7/a6mGKjNU0= -yunion.io/x/sqlchemy v0.0.0-20191226074733-6eb73845bfb7/go.mod h1:FTdwPdGhMgh4E+UFXc9klI1Ok34fMuybTT+jLhOaIjI= yunion.io/x/sqlchemy v0.0.0-20200114051901-dfdc01cac3c3 h1:4OA/7udM060GPhj1hqX80Tu+lC2ziAcMrKpo+EG5LZA= yunion.io/x/sqlchemy v0.0.0-20200114051901-dfdc01cac3c3/go.mod h1:FTdwPdGhMgh4E+UFXc9klI1Ok34fMuybTT+jLhOaIjI= yunion.io/x/structarg v0.0.0-20190809075558-115bed041de3 h1:bfC8EhXYvyGYldRWlzxiCM39Zfj3s3+zham9mW2h2LE= diff --git a/pkg/compute/guestdrivers/managedvirtual.go b/pkg/compute/guestdrivers/managedvirtual.go index b7e9e74e7a..62e614fffd 100644 --- a/pkg/compute/guestdrivers/managedvirtual.go +++ b/pkg/compute/guestdrivers/managedvirtual.go @@ -634,6 +634,7 @@ func (self *SManagedVirtualizedGuestDriver) RequestUndeployGuestOnHost(ctx conte if errors.Cause(err) == cloudprovider.ErrNotFound { return nil, nil } + log.Errorf("ihost.GetIVMById fail %s", err) return nil, err } diff --git a/pkg/multicloud/ctyun/disk.go b/pkg/multicloud/ctyun/disk.go index 32b99ebe57..9ffd0e33aa 100644 --- a/pkg/multicloud/ctyun/disk.go +++ b/pkg/multicloud/ctyun/disk.go @@ -16,10 +16,13 @@ package ctyun import ( "context" + "fmt" + "strconv" "time" "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" billing_api "yunion.io/x/onecloud/pkg/apis/billing" api "yunion.io/x/onecloud/pkg/apis/compute" @@ -257,13 +260,44 @@ func (self *SDisk) GetAccessPath() string { } func (self *SDisk) Delete(ctx context.Context) error { - return cloudprovider.ErrNotImplemented + _, err := self.storage.zone.region.DeleteDisk(self.GetId()) + if err != nil { + return errors.Wrap(err, "SDisk.Delete") + } + + return nil } func (self *SDisk) CreateISnapshot(ctx context.Context, name string, desc string) (cloudprovider.ICloudSnapshot, error) { - return nil, cloudprovider.ErrNotImplemented + jobId, err := self.storage.zone.region.CreateSnapshot(name, self.GetId(), desc) + if err != nil { + return nil, errors.Wrap(err, "Disk.CreateISnapshot.CreateSnapshot") + } + + snapshotId := "" + err = cloudprovider.Wait(10*time.Second, 1800*time.Second, func() (b bool, err error) { + statusJson, err := self.storage.zone.region.GetVbsJob(jobId) + if err != nil { + return false, err + } + + if status, _ := statusJson.GetString("status"); status == "SUCCESS" { + snapshotId, _ = statusJson.GetString("entities", "snapshot_id") + return true, nil + } else if status == "FAILED" { + return false, fmt.Errorf("CreateSnapshot job %s failed", jobId) + } else { + return false, nil + } + }) + if err != nil { + return nil, errors.Wrap(err, "Disk.CreateISnapshot.Wait") + } + + return self.storage.zone.region.GetSnapshot(self.GetId(), snapshotId) } +// POST http://ctyun-api-url/apiproxy/v3/ondemand/createVBS func (self *SDisk) GetISnapshot(idStr string) (cloudprovider.ICloudSnapshot, error) { return self.storage.zone.region.GetSnapshot(self.GetId(), idStr) } @@ -289,15 +323,61 @@ func (self *SDisk) GetExtSnapshotPolicyIds() ([]string, error) { } func (self *SDisk) Resize(ctx context.Context, newSizeMB int64) error { - return cloudprovider.ErrNotImplemented + jobId, err := self.storage.zone.region.ResizeDisk(self.GetId(), strconv.Itoa(int(newSizeMB/1024))) + if err != nil { + return errors.Wrap(err, "Disk.Resize") + } + + err = cloudprovider.Wait(10*time.Second, 1800*time.Second, func() (b bool, err error) { + statusJson, err := self.storage.zone.region.GetVbsJob(jobId) + if err != nil { + return false, err + } + + if status, _ := statusJson.GetString("status"); status == "SUCCESS" { + return true, nil + } else if status == "FAILED" { + return false, fmt.Errorf("Resize job %s failed", jobId) + } else { + return false, nil + } + }) + if err != nil { + return errors.Wrap(err, "Disk.Resize.Wait") + } + + return nil } func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) { - return "", cloudprovider.ErrNotImplemented + jobId, err := self.storage.zone.region.RestoreDisk(self.GetId(), snapshotId) + if err != nil { + return "", errors.Wrap(err, "Disk.Reset") + } + + err = cloudprovider.Wait(10*time.Second, 1800*time.Second, func() (b bool, err error) { + statusJson, err := self.storage.zone.region.GetVbsJob(jobId) + if err != nil { + return false, err + } + + if status, _ := statusJson.GetString("status"); status == "SUCCESS" { + return true, nil + } else if status == "FAILED" { + return false, fmt.Errorf("Reset job %s failed", jobId) + } else { + return false, nil + } + }) + if err != nil { + return "", errors.Wrap(err, "Disk.Reset.Wait") + } + + return self.GetId(), nil } func (self *SDisk) Rebuild(ctx context.Context) error { - return cloudprovider.ErrNotImplemented + return cloudprovider.ErrNotSupported } func (self *SDisk) GetDiskDetails() (*DiskDetails, error) { @@ -414,26 +494,140 @@ func (self *SRegion) CreateDisk(zoneId, name, diskType, size string) (*SDisk, er "createVolumeInfo": diskParams, } - resp, err := self.client.DoPost("/apiproxy/v3/ondemand/createVolume", params) + disks, err := self.GetDisks() + if err != nil { + return nil, errors.Wrap(err, "SRegion.CreateDisk.GetDisks") + } + + diskIds := []string{} + for i := range disks { + diskIds = append(diskIds, disks[i].GetId()) + } + + _, err = self.client.DoPost("/apiproxy/v3/ondemand/createVolume", params) if err != nil { return nil, errors.Wrap(err, "Region.CreateDisk.DoPost") } - disk := &SDisk{} - err = resp.Unmarshal(disk) - if err != nil { - return nil, errors.Wrap(err, "Region.CreateDisk.Unmarshal") - } + // 查询job结果一直报错,目前先用替代办法查找新硬盘ID,可能不准确。后续需要替换其它方法 + diskId := "" + cloudprovider.Wait(3*time.Second, 300*time.Second, func() (b bool, err error) { + disks, err := self.GetDisks() + if err != nil { + return false, err + } - izone, err := self.GetIZoneById(disk.AvailabilityZone) - if err != nil { - return nil, errors.Wrap(err, "SRegion.CreateDisk.GetIZoneById") - } + for i := range disks { + if !utils.IsInStringArray(disks[i].GetId(), diskIds) { + diskId = disks[i].GetId() + return true, nil + } + } - disk.storage = &SStorage{ - zone: izone.(*SZone), - storageType: disk.VolumeType, - } + return false, nil + }) - return disk, nil + return self.GetDisk(diskId) +} + +func (self *SRegion) CreateSnapshot(name, volumeId, desc string) (string, error) { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "volumeId": jsonutils.NewString(volumeId), + "name": jsonutils.NewString(name), + "description": jsonutils.NewString(desc), + } + + resp, err := self.client.DoPost("/apiproxy/v3/ondemand/createVBS", params) + if err != nil { + return "", errors.Wrap(err, "Region.CreateSnapshot.DoPost") + } + + var jobId string + err = resp.Unmarshal(&jobId, "returnObj", "data") + if err != nil { + return "", errors.Wrap(err, "Region.CreateSnapshot.Unmarshal") + } + + return jobId, nil +} + +func (self *SRegion) DeleteDisk(volumeId string) (string, error) { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "volumeId": jsonutils.NewString(volumeId), + } + + resp, err := self.client.DoPost("/apiproxy/v3/ondemand/deleteVolume", params) + if err != nil { + msg, _ := resp.GetString("message") + return "", errors.Wrap(fmt.Errorf(msg), "SRegion.DeleteDisk.DoPost") + } + + var jobId string + err = resp.Unmarshal(&jobId, "returnObj", "data") + if err != nil { + return "", errors.Wrap(err, "SRegion.DeleteDisk.Unmarshal") + } + + return jobId, nil +} + +func (self *SRegion) ResizeDisk(volumeId string, newSizeGB string) (string, error) { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "volumeId": jsonutils.NewString(volumeId), + "newSize": jsonutils.NewString(newSizeGB), + } + + resp, err := self.client.DoPost("/apiproxy/v3/ondemand/expandVolumeSize", params) + if err != nil { + return "", errors.Wrap(err, "SRegion.ResizeDisk.DoPost") + } + + var ok bool + err = resp.Unmarshal(&ok, "returnObj", "status") + if !ok { + msg, _ := resp.GetString("message") + return "", errors.Wrap(fmt.Errorf(msg), "SRegion.ResizeDisk.JobFailed") + } + + var jobId string + err = resp.Unmarshal(&jobId, "returnObj", "data") + if err != nil { + return "", errors.Wrap(err, "SRegion.ResizeDisk.Unmarshal") + } + + return jobId, nil +} + +func (self *SRegion) RestoreDisk(volumeId, backupId string) (string, error) { + diskBackupParams := jsonutils.NewDict() + diskBackupParams.Set("regionId", jsonutils.NewString(self.GetId())) + diskBackupParams.Set("backupId", jsonutils.NewString(backupId)) + diskBackupParams.Set("volumeId", jsonutils.NewString(volumeId)) + + params := map[string]jsonutils.JSONObject{ + "diskBackup": diskBackupParams, + } + + resp, err := self.client.DoPost("/apiproxy/v3/restoreDiskBackup", params) + if err != nil { + return "", errors.Wrap(err, "SRegion.RestoreDisk.DoPost") + } + + var ok bool + err = resp.Unmarshal(&ok, "returnObj", "status") + if !ok { + msg, _ := resp.GetString("message") + return "", errors.Wrap(fmt.Errorf(msg), "SRegion.RestoreDisk.JobFailed") + } + + var jobId string + err = resp.Unmarshal(&jobId, "returnObj", "data") + if err != nil { + return "", errors.Wrap(err, "SRegion.RestoreDisk.Unmarshal") + } + + return jobId, nil } diff --git a/pkg/multicloud/ctyun/eip.go b/pkg/multicloud/ctyun/eip.go index 1bd480aa99..3eb9a1727b 100644 --- a/pkg/multicloud/ctyun/eip.go +++ b/pkg/multicloud/ctyun/eip.go @@ -15,6 +15,8 @@ package ctyun import ( + "fmt" + "strconv" "strings" "time" @@ -185,19 +187,34 @@ func (self *SEip) GetInternetChargeType() string { } func (self *SEip) Delete() error { - return cloudprovider.ErrNotImplemented + return self.region.DeleteEip(self.GetId()) } func (self *SEip) Associate(instanceId string) error { - return cloudprovider.ErrNotImplemented + nics, err := self.region.GetNics(instanceId) + if err != nil { + return errors.Wrap(err, "Eip.Associate.GetNics") + } + + if len(nics) == 0 { + return errors.Wrap(fmt.Errorf("no network card found"), "Eip.Associate.GetINics") + } + + return self.region.AssociateEip(self.GetId(), nics[0].PortID) } func (self *SEip) Dissociate() error { - return cloudprovider.ErrNotImplemented + err := self.region.DissociateEip(self.GetId()) + if err != nil { + return err + } + + err = cloudprovider.WaitStatusWithDelay(self, api.EIP_STATUS_READY, 5*time.Second, 10*time.Second, 180*time.Second) + return err } func (self *SEip) ChangeBandwidth(bw int) error { - return cloudprovider.ErrNotImplemented + return self.region.ChangeBandwidthEip(self.GetName(), self.GetId(), strconv.Itoa(bw)) } type Profile struct { @@ -257,7 +274,7 @@ func (self *SRegion) GetEip(eipId string) (*SEip, error) { } } -func (self *SRegion) CreateEip(zoneId, name, size, shareType string) (*SEip, error) { +func (self *SRegion) CreateEip(zoneId, name, size, shareType, chargeType string) (*SEip, error) { eipParams := jsonutils.NewDict() eipParams.Set("regionId", jsonutils.NewString(self.GetId())) eipParams.Set("zoneId", jsonutils.NewString(zoneId)) @@ -265,22 +282,113 @@ func (self *SRegion) CreateEip(zoneId, name, size, shareType string) (*SEip, err eipParams.Set("type", jsonutils.NewString("5_telcom")) eipParams.Set("size", jsonutils.NewString(size)) eipParams.Set("shareType", jsonutils.NewString(shareType)) + eipParams.Set("chargeMode", jsonutils.NewString(chargeType)) params := map[string]jsonutils.JSONObject{ "createIpInfo": eipParams, } - eip := &SEip{} resp, err := self.client.DoPost("/apiproxy/v3/ondemand/createIp", params) if err != nil { return nil, errors.Wrap(err, "SRegion.CreateEip.DoPost") } - err = resp.Unmarshal(eip, "returnObj") + eipId, err := resp.GetString("returnObj", "id") if err != nil { - return nil, errors.Wrap(err, "SRegion.CreateEip.Unmarshal") + return nil, errors.Wrap(err, "SRegion.CreateEip.GetEipId") } - eip.region = self - return eip, nil + return self.GetEip(eipId) +} + +func (self *SRegion) DeleteEip(publicIpId string) error { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "publicIpId": jsonutils.NewString(publicIpId), + } + + resp, err := self.client.DoPost("/apiproxy/v3/ondemand/deleteIp", params) + if err != nil { + return errors.Wrap(err, "SRegion.DeleteEip.DoPost") + } + + var ok bool + err = resp.Unmarshal(&ok, "returnObj") + if !ok { + msg, _ := resp.GetString("message") + return errors.Wrap(fmt.Errorf(msg), "SRegion.DeleteEip.JobFailed") + } + + return nil +} + +// 这里networkCardId 实际指的是port id +func (self *SRegion) AssociateEip(publicIpId, networkCardId string) error { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "publicIpId": jsonutils.NewString(publicIpId), + "networkCardId": jsonutils.NewString(networkCardId), + } + + _, err := self.client.DoPost("/apiproxy/v3/ondemand/bindIp", params) + if err != nil { + return errors.Wrap(err, "SRegion.AssociateEip.DoPost") + } + + return nil +} + +func (self *SRegion) DissociateEip(publicIpId string) error { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "publicIpId": jsonutils.NewString(publicIpId), + } + + _, err := self.client.DoPost("/apiproxy/v3/ondemand/unbindIp", params) + if err != nil { + return errors.Wrap(err, "SRegion.DissociateEip.DoPost") + } + + return nil +} + +type SBandwidth struct { + PublicipInfo []PublicipInfo `json:"publicip_info"` + EnterpriseProjectID string `json:"enterprise_project_id"` + Name string `json:"name"` + ID string `json:"id"` + ShareType string `json:"share_type"` + Size int64 `json:"size"` + TenantID string `json:"tenant_id"` + ChargeMode string `json:"charge_mode"` + BandwidthType string `json:"bandwidth_type"` +} + +type PublicipInfo struct { + PublicipType string `json:"publicip_type"` + PublicipAddress string `json:"publicip_address"` + IPVersion int64 `json:"ip_version"` + PublicipID string `json:"publicip_id"` +} + +func (self *SRegion) ChangeBandwidthEip(name, publicIpId, sizeMb string) error { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "publicIpId": jsonutils.NewString(publicIpId), + "name": jsonutils.NewString(name), + "size": jsonutils.NewString(sizeMb), + } + + resp, err := self.client.DoPost("/apiproxy/v3/ondemand/upgradeNetwork", params) + if err != nil { + return errors.Wrap(err, "SRegion.ChangeBandwidthEip.DoPost") + } + + bandwidth := SBandwidth{} + err = resp.Unmarshal(&bandwidth, "returnObj") + if err != nil { + return errors.Wrap(err, "SRegion.ChangeBandwidthEip.Unmarshal") + } + + return nil } diff --git a/pkg/multicloud/ctyun/host.go b/pkg/multicloud/ctyun/host.go index f0460e3d5a..a4bb0b2219 100644 --- a/pkg/multicloud/ctyun/host.go +++ b/pkg/multicloud/ctyun/host.go @@ -16,6 +16,8 @@ package ctyun import ( "fmt" + "strings" + "time" "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" @@ -160,7 +162,53 @@ func (self *SHost) GetVersion() string { } func (self *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudprovider.ICloudVM, error) { - return nil, cloudprovider.ErrNotImplemented + network, err := self.zone.region.GetNetwork(desc.ExternalNetworkId) + if err != nil { + return nil, errors.Wrap(err, "Host.CreateVM.GetNetwork") + } + + jobId, err := self.zone.region.CreateInstance(self.zone.GetId(), desc.Name, desc.ExternalImageId, desc.OsType, desc.SysDisk.StorageType, desc.InstanceType, network.VpcID, desc.ExternalNetworkId, desc.ExternalSecgroupId, desc.Password) + if err != nil { + return nil, errors.Wrap(err, "Host.CreateVM.CreateInstance") + } + + vmId := "" + err = cloudprovider.Wait(10*time.Second, 600*time.Second, func() (b bool, err error) { + statusJson, err := self.zone.region.GetJob(jobId) + if err != nil { + if strings.Contains(err.Error(), "job fail") { + return false, err + } + + return false, nil + } + + if status, _ := statusJson.GetString("status"); status == "SUCCESS" { + jobs, err := statusJson.GetArray("entities", "sub_jobs") + if err != nil { + return false, err + } + + if len(jobs) > 0 { + vmId, err = jobs[0].GetString("entities", "server_id") + if err != nil { + return false, err + } + } else { + return false, fmt.Errorf("CreateVM empty sub jobs") + } + return true, nil + } else if status == "FAILED" { + return false, fmt.Errorf("CreateVM job %s failed", jobId) + } else { + return false, nil + } + }) + if err != nil { + return nil, errors.Wrap(err, "Disk.CreateISnapshot.Wait") + } + + return self.zone.region.GetVMById(vmId) } func (self *SHost) GetIHostNics() ([]cloudprovider.ICloudHostNetInterface, error) { diff --git a/pkg/multicloud/ctyun/image.go b/pkg/multicloud/ctyun/image.go index 3f72819977..c9d9a5326f 100644 --- a/pkg/multicloud/ctyun/image.go +++ b/pkg/multicloud/ctyun/image.go @@ -98,7 +98,7 @@ func (self *SImage) GetMetadata() *jsonutils.JSONDict { } func (self *SImage) Delete(ctx context.Context) error { - return cloudprovider.ErrNotImplemented + return cloudprovider.ErrNotSupported } func (self *SImage) GetIStoragecache() cloudprovider.ICloudStoragecache { diff --git a/pkg/multicloud/ctyun/instance.go b/pkg/multicloud/ctyun/instance.go index fca81ee1cb..dc80ba63ae 100644 --- a/pkg/multicloud/ctyun/instance.go +++ b/pkg/multicloud/ctyun/instance.go @@ -17,6 +17,7 @@ package ctyun import ( "context" "fmt" + "strconv" "strings" "time" @@ -402,11 +403,18 @@ func (self *SInstance) getSecurityGroupIdsByMasterOrderId(orderId string) ([]str } func (self *SInstance) AssignSecurityGroup(secgroupId string) error { - return cloudprovider.ErrNotImplemented + return self.host.zone.region.AssignSecurityGroup(self.GetId(), secgroupId) } func (self *SInstance) SetSecurityGroups(secgroupIds []string) error { - return cloudprovider.ErrNotImplemented + for i := 0; i < len(secgroupIds); i++ { + err := self.host.zone.region.AssignSecurityGroup(self.GetId(), secgroupIds[i]) + if err != nil { + return errors.Wrap(err, "Instance.SetSecurityGroups") + } + } + + return nil } func (self *SInstance) GetHypervisor() string { @@ -414,35 +422,144 @@ func (self *SInstance) GetHypervisor() string { } func (self *SInstance) StartVM(ctx context.Context) error { - return cloudprovider.ErrNotImplemented + err := self.host.zone.region.StartVM(self.GetId()) + if err != nil { + return errors.Wrap(err, "Instance.StartVM") + } + + return cloudprovider.WaitStatus(self, api.VM_RUNNING, 5*time.Second, 300*time.Second) } func (self *SInstance) StopVM(ctx context.Context, isForce bool) error { - return cloudprovider.ErrNotImplemented + err := self.host.zone.region.StopVM(self.GetId()) + if err != nil { + return errors.Wrap(err, "Instance.StopVM") + } + + return cloudprovider.WaitStatus(self, api.VM_READY, 5*time.Second, 300*time.Second) } func (self *SInstance) DeleteVM(ctx context.Context) error { - return cloudprovider.ErrNotImplemented + err := self.host.zone.region.DeleteVM(self.GetId()) + if err != nil { + return errors.Wrap(err, "SInstance.DeleteVM") + } + + return cloudprovider.WaitDeleted(self, 10*time.Second, 180*time.Second) } func (self *SInstance) UpdateVM(ctx context.Context, name string) error { - return cloudprovider.ErrNotImplemented + return cloudprovider.ErrNotSupported } func (self *SInstance) UpdateUserData(userData string) error { - return cloudprovider.ErrNotImplemented + return cloudprovider.ErrNotSupported } -func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) { - return "", cloudprovider.ErrNotImplemented +func (self *SInstance) RebuildRoot(ctx context.Context, config *cloudprovider.SManagedVMRebuildRootConfig) (string, error) { + currentImage, err := self.GetImage() + if err != nil { + return "", errors.Wrap(err, "Instance.RebuildRoot") + } + + publicKeyName := "" + if len(config.PublicKey) > 0 { + publicKeyName, err = self.host.zone.region.syncKeypair(config.PublicKey) + if err != nil { + return "", errors.Wrap(err, "Instance.RebuildRoot.syncKeypair") + } + } + + jobId := "" + if currentImage.GetId() != config.ImageId { + jobId, err = self.host.zone.region.SwitchVMOs(self.GetId(), config.Password, publicKeyName, config.ImageId) + if err != nil { + return "", errors.Wrap(err, "SInstance.RebuildRoot.SwitchVMOs") + } + } else { + jobId, err = self.host.zone.region.RebuildVM(self.GetId(), config.Password, publicKeyName) + if err != nil { + return "", errors.Wrap(err, "SInstance.RebuildRoot.RebuildVM") + } + } + + err = cloudprovider.Wait(10*time.Second, 1800*time.Second, func() (b bool, err error) { + statusJson, err := self.host.zone.region.GetJob(jobId) + if err != nil { + if strings.Contains(err.Error(), "job fail") { + return false, err + } + + return false, nil + } + + if status, _ := statusJson.GetString("status"); status == "SUCCESS" { + return true, nil + } else if status == "FAILED" { + return false, fmt.Errorf("RebuildRoot job %s failed", jobId) + } else { + return false, nil + } + }) + if err != nil { + return "", errors.Wrap(err, "Instance.RebuildRoot.Wait") + } + + err = self.Refresh() + if err != nil { + return "", err + } + + idisks, err := self.GetIDisks() + if err != nil { + return "", err + } + + if len(idisks) == 0 { + return "", fmt.Errorf("server %s has no volume attached.", self.GetId()) + } + + return idisks[0].GetId(), nil } func (self *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error { - return cloudprovider.ErrNotImplemented + if len(password) == 0 { + return cloudprovider.ErrNotSupported + } + + // 只支持重置密码 + return self.host.zone.region.ResetVMPassword(self.GetId(), password) } func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error { - return cloudprovider.ErrNotImplemented + jobId, err := self.host.zone.region.ChangeVMConfig(self.GetId(), config.InstanceType) + if err != nil { + return errors.Wrap(err, "Instance.ChangeConfig") + } + + err = cloudprovider.Wait(10*time.Second, 1800*time.Second, func() (b bool, err error) { + statusJson, err := self.host.zone.region.GetJob(jobId) + if err != nil { + if strings.Contains(err.Error(), "job fail") { + return false, err + } + + return false, nil + } + + if status, _ := statusJson.GetString("status"); status == "SUCCESS" { + return true, nil + } else if status == "FAILED" { + return false, fmt.Errorf("ChangeConfig job %s failed", jobId) + } else { + return false, nil + } + }) + if err != nil { + return errors.Wrap(err, "Instance.ChangeConfig.Wait") + } + + return nil } // http://ctyun-api-url/apiproxy/v3/queryVncUrl @@ -458,20 +575,113 @@ func (self *SInstance) GetVNCInfo() (jsonutils.JSONObject, error) { return ret, nil } +func (self *SInstance) NextDeviceName() (string, error) { + details, err := self.GetDetails() + if err != nil { + return "", errors.Wrap(err, "SInstance.NextDeviceName.GetDetails") + } + + disks := []*SDisk{} + for i := range details.Volumes { + disk, err := self.host.zone.region.GetDisk(details.Volumes[i].ID) + if err != nil { + return "", errors.Wrap(err, "SInstance.NextDeviceName.GetDisk") + } + + disks = append(disks, disk) + } + + prefix := "s" + if len(disks) > 0 && strings.Contains(disks[0].GetMountpoint(), "/vd") { + prefix = "v" + } + + currents := []string{} + for _, disk := range disks { + currents = append(currents, strings.ToLower(disk.GetMountpoint())) + } + + for i := 0; i < 25; i++ { + device := fmt.Sprintf("/dev/%sd%s", prefix, string(98+i)) + if ok, _ := utils.InStringArray(device, currents); !ok { + return device, nil + } + } + + return "", fmt.Errorf("disk devicename out of index, current deivces: %s", currents) +} + func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error { - return cloudprovider.ErrNotImplemented + device, err := self.NextDeviceName() + if err != nil { + return errors.Wrap(err, "Instance.AttachDisk.NextDeviceName") + } + + _, err = self.host.zone.region.AttachDisk(self.GetId(), diskId, device) + if err != nil { + return errors.Wrap(err, "Instance.AttachDisk") + } + + disk, err := self.host.zone.region.GetDisk(diskId) + if err != nil { + return errors.Wrap(err, "AttachDisk.GetDisk") + } + + err = cloudprovider.WaitStatusWithDelay(disk, api.DISK_READY, 10*time.Second, 5*time.Second, 180*time.Second) + if err != nil { + return errors.Wrap(err, "Instance.DetachDisk.WaitStatusWithDelay") + } + + if disk.Status != "in-use" { + return errors.Wrap(fmt.Errorf("disk status %s", disk.Status), "Instance.DetachDisk.Status") + } + + return nil } func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error { - return cloudprovider.ErrNotImplemented + disk, err := self.host.zone.region.GetDisk(diskId) + if err != nil { + return errors.Wrap(err, "DetachDisk.Wait") + } + + if len(disk.Attachments) == 0 { + return errors.Wrap(err, "Instance.DetachDisk") + } + + _, err = self.host.zone.region.DetachDisk(self.GetId(), diskId, disk.Attachments[0].Device) + if err != nil { + return errors.Wrap(err, "Instance.DetachDisk") + } + + err = cloudprovider.WaitStatusWithDelay(disk, api.DISK_READY, 10*time.Second, 5*time.Second, 180*time.Second) + if err != nil { + return errors.Wrap(err, "Instance.DetachDisk.WaitStatusWithDelay") + } + + if disk.Status != "available" { + return errors.Wrap(fmt.Errorf("disk status %s", disk.Status), "Instance.DetachDisk.Status") + } + + return nil } func (self *SInstance) CreateDisk(ctx context.Context, sizeMb int, uuid string, driver string) error { - return cloudprovider.ErrNotImplemented + _, err := self.host.zone.region.CreateDisk(self.host.zone.GetId(), uuid, driver, strconv.Itoa(sizeMb)) + if err != nil { + return errors.Wrap(err, "Instance.CreateDisk") + } + + return err } func (self *SInstance) Renew(bc billing.SBillingCycle) error { - return cloudprovider.ErrNotImplemented + _, err := self.host.zone.region.RenewVM(self.GetId(), &bc) + if err != nil { + return errors.Wrap(err, "Instance.Renew.RenewVM") + } + + return nil } func (self *SInstance) GetError() error { @@ -549,7 +759,11 @@ func (self *SRegion) GetInstanceVNCUrl(vmId string) (string, error) { return ret.URL, nil } -func (self *SRegion) CreateInstance(zoneId, name, imageId, volumetype, flavorRef, vpcid, subnetId, secGroupId, adminPass string) error { +/* +创建主机接口目前没有绑定密钥的参数选项,不支持绑定密码。 +但是重装系统接口支持绑定密钥 +*/ +func (self *SRegion) CreateInstance(zoneId, name, imageId, osType, volumetype, flavorRef, vpcid, subnetId, secGroupId, adminPass string) (string, error) { rootParams := jsonutils.NewDict() rootParams.Set("volumetype", jsonutils.NewString(volumetype)) @@ -572,8 +786,7 @@ func (self *SRegion) CreateInstance(zoneId, name, imageId, volumetype, flavorRef serverParams.Set("imageRef", jsonutils.NewString(imageId)) serverParams.Set("root_volume", rootParams) serverParams.Set("flavorRef", jsonutils.NewString(flavorRef)) - // todo: fix me - serverParams.Set("osType", jsonutils.NewString("Linux")) + serverParams.Set("osType", jsonutils.NewString(osType)) serverParams.Set("vpcid", jsonutils.NewString(vpcid)) serverParams.Set("security_groups", secgroupParams) serverParams.Set("nics", nicParams) @@ -588,12 +801,25 @@ func (self *SRegion) CreateInstance(zoneId, name, imageId, volumetype, flavorRef "createVMInfo": vmParams, } - _, err := self.client.DoPost("/apiproxy/v3/ondemand/createVM", params) + resp, err := self.client.DoPost("/apiproxy/v3/ondemand/createVM", params) if err != nil { - return errors.Wrap(err, "SRegion.CreateInstance.DoPost") + return "", errors.Wrap(err, "SRegion.CreateInstance.DoPost") } - return nil + var ok bool + err = resp.Unmarshal(&ok, "returnObj", "status") + if !ok { + msg, _ := resp.GetString("returnObj", "message") + return "", errors.Wrap(fmt.Errorf(msg), "SRegion.CreateInstance.JobFailed") + } + + var jobId string + err = resp.Unmarshal(&jobId, "returnObj", "data") + if err != nil { + return "", errors.Wrap(err, "SRegion.CreateInstance.Unmarshal") + } + + return jobId, nil } // vm & nic job @@ -637,3 +863,337 @@ func (self *SRegion) GetVbsJob(jobId string) (jsonutils.JSONObject, error) { return ret, nil } + +// POST http://ctyun-api-url/apiproxy/v3/addSecurityGroup 绑定安全组 +func (self *SRegion) AssignSecurityGroup(vmId, securityGroupRuleId string) error { + securityParams := jsonutils.NewDict() + securityParams.Set("regionId", jsonutils.NewString(self.GetId())) + securityParams.Set("vmId", jsonutils.NewString(vmId)) + securityParams.Set("securityGroupRuleId", jsonutils.NewString(securityGroupRuleId)) + + params := map[string]jsonutils.JSONObject{ + "securityGroup": securityParams, + } + + _, err := self.client.DoPost("/apiproxy/v3/addSecurityGroup", params) + if err != nil { + return errors.Wrap(err, "SRegion.AssignSecurityGroup.DoPost") + } + + return nil +} + +// POST http://ctyun-api-url/apiproxy/v3/removeSecurityGroup 解绑安全组 +func (self *SRegion) UnsignSecurityGroup(vmId, securityGroupRuleId string) error { + securityParams := jsonutils.NewDict() + securityParams.Set("regionId", jsonutils.NewString(self.GetId())) + securityParams.Set("vmId", jsonutils.NewString(vmId)) + securityParams.Set("securityGroupRuleId", jsonutils.NewString(securityGroupRuleId)) + + params := map[string]jsonutils.JSONObject{ + "securityGroup": securityParams, + } + + _, err := self.client.DoPost("/apiproxy/v3/removeSecurityGroup", params) + if err != nil { + return errors.Wrap(err, "SRegion.UnsignSecurityGroup.DoPost") + } + + return nil +} + +func (self *SRegion) StartVM(vmId string) error { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "vmId": jsonutils.NewString(vmId), + } + + _, err := self.client.DoPost("/apiproxy/v3/ondemand/startVM", params) + if err != nil { + return errors.Wrap(err, "SRegion.StartVm.DoPost") + } + + return nil +} + +func (self *SRegion) StopVM(vmId string) error { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "vmId": jsonutils.NewString(vmId), + } + + _, err := self.client.DoPost("/apiproxy/v3/ondemand/stopVM", params) + if err != nil { + return errors.Wrap(err, "SRegion.StopVM.DoPost") + } + + return nil +} + +func (self *SRegion) DeleteVM(vmId string) error { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "vmId": jsonutils.NewString(vmId), + } + + _, err := self.client.DoPost("/apiproxy/v3/ondemand/deleteVM", params) + if err != nil { + return errors.Wrap(err, "SRegion.DeleteVM.DoPost") + } + + return nil +} + +func (self *SRegion) RestartVM(vmId string) error { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "vmId": jsonutils.NewString(vmId), + "type": jsonutils.NewString("SOFT"), + } + + _, err := self.client.DoPost("/apiproxy/v3/ondemand/restartVM", params) + if err != nil { + return errors.Wrap(err, "SRegion.RestartVM.DoPost") + } + + return nil +} + +func (self *SRegion) SwitchVMOs(vmId, adminPass, keyName, imageRef string) (string, error) { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "vmId": jsonutils.NewString(vmId), + "imageRef": jsonutils.NewString(imageRef), + } + + if len(keyName) > 0 { + params["keyName"] = jsonutils.NewString(keyName) + } else if len(adminPass) > 0 { + params["adminPass"] = jsonutils.NewString(adminPass) + } else { + return "", errors.Wrap(fmt.Errorf("require public key or password"), "SRegion.SwitchVMOs") + } + + resp, err := self.client.DoPost("/apiproxy/v3/ondemand/switchSys", params) + if err != nil { + return "", errors.Wrap(err, "SRegion.SwitchVMOs.DoPost") + } + + var ok bool + err = resp.Unmarshal(&ok, "returnObj", "status") + if !ok { + msg, _ := resp.GetString("returnObj", "message") + return "", errors.Wrap(fmt.Errorf(msg), "SRegion.SwitchVMOs.JobFailed") + } + + var jobId string + err = resp.Unmarshal(&jobId, "returnObj", "data") + if err != nil { + return "", errors.Wrap(err, "SRegion.SwitchVMOs.Unmarshal") + } + + return jobId, nil +} + +func (self *SRegion) RebuildVM(vmId, adminPass, keyName string) (string, error) { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "vmId": jsonutils.NewString(vmId), + } + + if len(keyName) > 0 { + params["keyName"] = jsonutils.NewString(keyName) + } else if len(adminPass) > 0 { + params["adminPass"] = jsonutils.NewString(adminPass) + } else { + return "", errors.Wrap(fmt.Errorf("require public key or password"), "SRegion.RebuildVM") + } + + resp, err := self.client.DoPost("/apiproxy/v3/ondemand/reInstallSys", params) + if err != nil { + return "", errors.Wrap(err, "SRegion.RebuildVM.DoPost") + } + + var ok bool + err = resp.Unmarshal(&ok, "returnObj", "status") + if !ok { + msg, _ := resp.GetString("returnObj", "message") + return "", errors.Wrap(fmt.Errorf(msg), "SRegion.RebuildVM.JobFailed") + } + + var jobId string + err = resp.Unmarshal(&jobId, "returnObj", "data") + if err != nil { + return "", errors.Wrap(err, "SRegion.RebuildVM.Unmarshal") + } + + return jobId, nil +} + +func (self *SRegion) AttachDisk(vmId, volumeId, device string) (string, error) { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "volumeId": jsonutils.NewString(volumeId), + "vmId": jsonutils.NewString(vmId), + "device": jsonutils.NewString(device), + } + + resp, err := self.client.DoPost("/apiproxy/v3/ondemand/attachVolume", params) + if err != nil { + return "", errors.Wrap(err, "SRegion.AttachDisk.DoPost") + } + + var ok bool + err = resp.Unmarshal(&ok, "returnObj", "status") + if !ok { + msg, _ := resp.GetString("returnObj", "message") + return "", errors.Wrap(fmt.Errorf(msg), "SRegion.AttachDisk.JobFailed") + } + + var jobId string + err = resp.Unmarshal(&jobId, "returnObj", "data") + if err != nil { + return "", errors.Wrap(err, "SRegion.AttachDisk.Unmarshal") + } + + return jobId, nil +} + +func (self *SRegion) DetachDisk(vmId, volumeId, device string) (string, error) { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "volumeId": jsonutils.NewString(volumeId), + "vmId": jsonutils.NewString(vmId), + "device": jsonutils.NewString(device), + } + + resp, err := self.client.DoPost("/apiproxy/v3/ondemand/uninstallVolume", params) + if err != nil { + return "", errors.Wrap(err, "SRegion.DetachDisk.DoPost") + } + + var ok bool + err = resp.Unmarshal(&ok, "returnObj", "status") + if !ok { + msg, _ := resp.GetString("returnObj", "message") + return "", errors.Wrap(fmt.Errorf(msg), "SRegion.DetachDisk.JobFailed") + } + + var jobId string + err = resp.Unmarshal(&jobId, "returnObj", "data") + if err != nil { + return "", errors.Wrap(err, "SRegion.DetachDisk.Unmarshal") + } + + return jobId, nil +} + +func (self *SRegion) ChangeVMConfig(vmId, flavorId string) (string, error) { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "vmId": jsonutils.NewString(vmId), + "flavorId": jsonutils.NewString(flavorId), + } + + resp, err := self.client.DoPost("/apiproxy/v3/ondemand/upgradeVM", params) + if err != nil { + return "", errors.Wrap(err, "SRegion.ChangeVMConfig.DoPost") + } + + var ok bool + err = resp.Unmarshal(&ok, "returnObj", "status") + if !ok { + msg, _ := resp.GetString("returnObj", "message") + return "", errors.Wrap(fmt.Errorf(msg), "SRegion.ChangeVMConfig.JobFailed") + } + + var jobId string + err = resp.Unmarshal(&jobId, "returnObj", "data") + if err != nil { + return "", errors.Wrap(err, "SRegion.ChangeVMConfig.Unmarshal") + } + + return jobId, nil +} + +// POST http://ctyun-api-url/apiproxy/v3/order/placeRenewOrder 续订 +func (self *SRegion) RenewVM(vmId string, bc *billing.SBillingCycle) ([]string, error) { + if bc == nil { + return nil, errors.Wrap(fmt.Errorf("SBillingCycle is nil"), "Region.RenewVM") + } + + resourcePackage := jsonutils.NewDict() + month := bc.GetMonths() + switch { + case month <= 11: + resourcePackage.Set("cycleCount", jsonutils.NewString(strconv.Itoa(month))) + resourcePackage.Set("cycleType", jsonutils.NewString("3")) + case month == 12: + resourcePackage.Set("cycleCount", jsonutils.NewString("1")) + resourcePackage.Set("cycleType", jsonutils.NewString("5")) + case month == 24: + resourcePackage.Set("cycleCount", jsonutils.NewString("1")) + resourcePackage.Set("cycleType", jsonutils.NewString("6")) + case month == 36: + resourcePackage.Set("cycleCount", jsonutils.NewString("1")) + resourcePackage.Set("cycleType", jsonutils.NewString("7")) + default: + return nil, errors.Wrap(fmt.Errorf("unsupported month duration %d. expected 1~11, 12, 24, 36", month), "Region.RenewVM") + } + + vmIds := jsonutils.NewArray() + vmIds.Add(jsonutils.NewString(vmId)) + resourcePackage.Set("resourceIds", vmIds) + + params := map[string]jsonutils.JSONObject{ + "resourceDetailJson": resourcePackage, + } + + resp, err := self.client.DoPost("/apiproxy/v3/order/placeRenewOrder", params) + if err != nil { + return nil, errors.Wrap(err, "SRegion.RenewVM.DoPost") + } + + var ok bool + err = resp.Unmarshal(&ok, "returnObj", "submitted") + if !ok { + msg, _ := resp.GetString("returnObj", "message") + return nil, errors.Wrap(fmt.Errorf(msg), "SRegion.RenewVM.JobFailed") + } + + type OrderPlacedEventsElement struct { + ErrorMessage string `json:"errorMessage"` + Submitted bool `json:"submitted"` + NewOrderID string `json:"newOrderId"` + NewOrderNo string `json:"newOrderNo"` + TotalPrice int64 `json:"totalPrice"` + } + + orders := []OrderPlacedEventsElement{} + err = resp.Unmarshal(&orders, "returnObj", "orderPlacedEvents") + if err != nil { + return nil, errors.Wrap(err, "SRegion.RenewVM.Unmarshal") + } + + orderIds := []string{} + for i := range orders { + orderIds = append(orderIds, orders[i].NewOrderID) + } + + return orderIds, nil +} + +func (self *SRegion) ResetVMPassword(vmId, password string) error { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "vmId": jsonutils.NewString(vmId), + "password": jsonutils.NewString(password), + } + + _, err := self.client.DoPost("/apiproxy/v3/resetVmPassword", params) + if err != nil { + return errors.Wrap(err, "SRegion.ResetVMPassword.DoPost") + } + + return nil +} diff --git a/pkg/multicloud/ctyun/keypair.go b/pkg/multicloud/ctyun/keypair.go index bd228d116c..50b6c3baa7 100644 --- a/pkg/multicloud/ctyun/keypair.go +++ b/pkg/multicloud/ctyun/keypair.go @@ -14,6 +14,140 @@ package ctyun -// http://ctyun-api-url/apiproxy/v3/order/getZoneConfig +import ( + "fmt" + "strconv" + "time" + + "github.com/aokoli/goutils" + "golang.org/x/crypto/ssh" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" +) + +// GET http://ctyun-api-url/apiproxy/v3/querySSH +// POST http://ctyun-api-url/apiproxy/v3/deleteSSH type SKeypair struct { + Fingerprint string `json:"fingerprint"` + Name string `json:"name"` + PublicKey string `json:"public_key"` +} + +func (self *SRegion) getFingerprint(publicKey string) (string, error) { + pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKey)) + if err != nil { + return "", fmt.Errorf("publicKey error %s", err) + } + + return ssh.FingerprintSHA256(pk), nil +} + +// GET http://ctyun-api-url/apiproxy/v3/querySSH +func (self *SRegion) GetKeypairs() ([]SKeypair, int, error) { + params := map[string]string{ + "regionId": self.GetId(), + } + + resp, err := self.client.DoGet("/apiproxy/v3/querySSH", params) + if err != nil { + return nil, 0, errors.Wrap(err, "SRegion.GetKeypairs.DoGet") + } + + keypairs := []jsonutils.JSONObject{} + err = resp.Unmarshal(&keypairs, "returnObj", "keypairs") + if err != nil { + return nil, 0, errors.Wrap(err, "SRegion.GetKeypairs.Unmarshal") + } + + ret := []SKeypair{} + for i := range keypairs { + k, err := keypairs[i].Get("keypair") + if err != nil { + return nil, 0, errors.Wrap(err, "SRegion.GetKeypairs") + } + + keypair := SKeypair{} + err = k.Unmarshal(&keypair) + if err != nil { + return nil, 0, errors.Wrap(err, "SRegion.GetKeypairs.Unmarshal") + } + + ret = append(ret, keypair) + } + + return ret, len(ret), nil +} + +func (self *SRegion) GetKeypair(name string) (*SKeypair, error) { + keypairs, _, err := self.GetKeypairs() + if err != nil { + return nil, errors.Wrap(err, "SRegion.GetKeypair.GetKeypairs") + } + + for i := range keypairs { + if keypairs[i].Name == name { + return &keypairs[i], nil + } + } + + return nil, errors.Wrap(errors.ErrNotFound, "SRegion.GetKeypair") +} + +func (self *SRegion) lookUpKeypair(publicKey string) (string, error) { + keypairs, _, err := self.GetKeypairs() + if err != nil { + return "", err + } + + fingerprint, err := self.getFingerprint(publicKey) + if err != nil { + return "", err + } + + for _, keypair := range keypairs { + if keypair.Fingerprint == fingerprint { + return keypair.Name, nil + } + } + + return "", fmt.Errorf("keypair not found %s", err) +} + +// POST http://ctyun-api-url/apiproxy/v3/createSSH +func (self *SRegion) ImportKeypair(name, publicKey string) (*SKeypair, error) { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "name": jsonutils.NewString(name), + "publicKey": jsonutils.NewString(publicKey), + } + + _, err := self.client.DoPost("/apiproxy/v3/createSSH", params) + if err != nil { + return nil, errors.Wrap(err, "SRegion.ImportKeypair.DoPost") + } + + return self.GetKeypair(name) +} + +func (self *SRegion) importKeypair(publicKey string) (string, error) { + prefix, e := goutils.RandomAlphabetic(6) + if e != nil { + return "", fmt.Errorf("publicKey error %s", e) + } + + name := prefix + strconv.FormatInt(time.Now().Unix(), 10) + if k, e := self.ImportKeypair(name, publicKey); e != nil { + return "", fmt.Errorf("keypair import error %s", e) + } else { + return k.Name, nil + } +} + +func (self *SRegion) syncKeypair(publicKey string) (string, error) { + name, e := self.lookUpKeypair(publicKey) + if e == nil { + return name, nil + } + return self.importKeypair(publicKey) } diff --git a/pkg/multicloud/ctyun/network.go b/pkg/multicloud/ctyun/network.go index 1d68107157..a798c77203 100644 --- a/pkg/multicloud/ctyun/network.go +++ b/pkg/multicloud/ctyun/network.go @@ -15,6 +15,10 @@ package ctyun import ( + "fmt" + "strconv" + "time" + "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" @@ -133,7 +137,7 @@ func (self *SNetwork) GetPublicScope() rbacutils.TRbacScope { } func (self *SNetwork) Delete() error { - return cloudprovider.ErrNotImplemented + return self.vpc.region.DeleteNetwork(self.vpc.GetId(), self.GetId()) } func (self *SNetwork) GetAllocTimeoutSeconds() int { @@ -218,13 +222,18 @@ func (self *SRegion) GetNetwork(subnetId string) (*SNetwork, error) { return network, err } -func (self *SRegion) CreateNetwork(vpcId, zoneId, name, cidr, gatewayIp, dhcpEnable string) (*SNetwork, error) { +func (self *SRegion) CreateNetwork(vpcId, zoneId, name, cidr, dhcpEnable string) (*SNetwork, error) { + gateway, err := getDefaultGateWay(cidr) + if err != nil { + return nil, err + } + networkParams := jsonutils.NewDict() networkParams.Set("regionId", jsonutils.NewString(self.GetId())) networkParams.Set("zoneId", jsonutils.NewString(zoneId)) networkParams.Set("name", jsonutils.NewString(name)) networkParams.Set("cidr", jsonutils.NewString(cidr)) - networkParams.Set("gatewayIp", jsonutils.NewString(gatewayIp)) + networkParams.Set("gatewayIp", jsonutils.NewString(gateway)) networkParams.Set("dhcpEnable", jsonutils.NewString(dhcpEnable)) networkParams.Set("vpcId", jsonutils.NewString(vpcId)) // DNS地址,如果主机需要访问公网就需要填写该值,不填写就不能使用DNS解析 @@ -235,16 +244,53 @@ func (self *SRegion) CreateNetwork(vpcId, zoneId, name, cidr, gatewayIp, dhcpEna "jsonStr": networkParams, } - network := &SNetwork{} resp, err := self.client.DoPost("/apiproxy/v3/createSubnet", params) if err != nil { return nil, errors.Wrap(err, "SRegion.CreateNetwork.DoPost") } - err = resp.Unmarshal(network, "returnObj") + netId, err := resp.GetString("returnObj", "id") if err != nil { - return nil, errors.Wrap(err, "SRegion.CreateNetwork.Unmarshal") + return nil, errors.Wrap(err, "SRegion.CreateNetwork.GetString") } - return network, err + err = cloudprovider.WaitCreated(10*time.Second, 180*time.Second, func() bool { + network, err := self.getNetwork(netId) + if err != nil { + log.Debugf("SRegion.CreateNetwork.getNetwork") + return false + } + + if len(network.VpcID) == 0 { + return false + } else { + return true + } + }) + if err != nil { + return nil, errors.Wrap(err, "SRegion.CreateNetwork.GetVpc") + } + + return self.GetNetwork(netId) +} + +func (self *SRegion) DeleteNetwork(vpcId, subnetId string) error { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "vpcId": jsonutils.NewString(vpcId), + "subnetId": jsonutils.NewString(subnetId), + } + + resp, err := self.client.DoPost("/apiproxy/v3/deleteSubnet", params) + if err != nil { + return errors.Wrap(err, "SRegion.DeleteNetwork.DoPost") + } + + var statusCode int + err = resp.Unmarshal(&statusCode, "statusCode") + if statusCode != 800 { + return errors.Wrap(fmt.Errorf(strconv.Itoa(statusCode)), "SRegion.DeleteNetwork.Failed") + } + + return nil } diff --git a/pkg/multicloud/ctyun/region.go b/pkg/multicloud/ctyun/region.go index db76a62a47..ffcdb1bb0e 100644 --- a/pkg/multicloud/ctyun/region.go +++ b/pkg/multicloud/ctyun/region.go @@ -16,6 +16,7 @@ package ctyun import ( "fmt" + "strconv" "yunion.io/x/jsonutils" "yunion.io/x/pkg/errors" @@ -142,7 +143,17 @@ func (self *SRegion) GetISecurityGroupByName(vpcId string, name string) (cloudpr } func (self *SRegion) CreateISecurityGroup(conf *cloudprovider.SecurityGroupCreateInput) (cloudprovider.ICloudSecurityGroup, error) { - return nil, cloudprovider.ErrNotImplemented + secgroup, err := self.CreateSecurityGroup(conf.VpcId, conf.Name) + if err != nil { + return nil, errors.Wrap(err, "Region.CreateISecurityGroup") + } + + err = self.syncSecgroupRules(secgroup.GetId(), conf.Rules) + if err != nil { + return nil, errors.Wrap(err, "Region.CreateISecurityGroup.syncSecgroupRules") + } + + return secgroup, nil } func (self *SRegion) GetId() string { @@ -257,20 +268,63 @@ func (self *SRegion) GetIDiskById(id string) (cloudprovider.ICloudDisk, error) { return self.GetDisk(id) } -func (self *SRegion) DeleteSecurityGroup(vpcId, secgroupId string) error { - return cloudprovider.ErrNotImplemented +func (self *SRegion) DeleteSecurityGroup(securityGroupId string) error { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "securityGroupId": jsonutils.NewString(securityGroupId), + } + + resp, err := self.client.DoPost("statusCode", params) + if err != nil { + return errors.Wrap(err, "SRegion.DeleteSecurityGroup.DoPost") + } + + var statusCode int + err = resp.Unmarshal(&statusCode, "statusCode") + if statusCode != 800 { + return errors.Wrap(fmt.Errorf(strconv.Itoa(statusCode)), "SRegion.DeleteSecurityGroup.JobFailed") + } + + return nil } func (self *SRegion) SyncSecurityGroup(secgroupId string, vpcId string, name string, desc string, rules []secrules.SecurityRule) (string, error) { - return "", cloudprovider.ErrNotImplemented + if len(secgroupId) > 0 { + _, err := self.GetSecurityGroupDetails(secgroupId) + if err == cloudprovider.ErrNotFound { + secgroupId = "" + } else if err != nil { + return "", errors.Wrapf(err, "self.GetSecurityGroupDetails(%s)", secgroupId) + } + } + + if len(secgroupId) == 0 { + secgroup, err := self.CreateSecurityGroup(vpcId, name) + if err != nil { + return "", errors.Wrap(err, "self.CreateSecurityGroup") + } + secgroupId = secgroup.GetId() + } + + rules = SecurityRuleSetToAllowSet(rules) + return secgroupId, self.syncSecgroupRules(secgroupId, rules) } func (self *SRegion) CreateIVpc(name string, desc string, cidr string) (cloudprovider.ICloudVpc, error) { - return nil, cloudprovider.ErrNotImplemented + return self.CreateVpc(name, cidr) } func (self *SRegion) CreateEIP(eip *cloudprovider.SEip) (cloudprovider.ICloudEIP, error) { - return nil, cloudprovider.ErrNotImplemented + zones, err := self.GetIZones() + if err != nil { + return nil, errors.Wrap(err, "SRegion.CreateEIP.GetIZones") + } + + if len(zones) == 0 { + return nil, errors.Wrap(errors.ErrNotFound, "SRegion.CreateEIP.GetIZones") + } + + return self.CreateEip(zones[0].GetId(), eip.Name, strconv.Itoa(eip.BandwidthMbps), "PER", eip.ChargeType) } func (self *SRegion) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { @@ -310,7 +364,7 @@ func (self *SRegion) UpdateSnapshotPolicy(*cloudprovider.SnapshotPolicyInput, st return cloudprovider.ErrNotImplemented } -func (self *SRegion) DeleteSnapshotPolicy(string) error { +func (self *SRegion) DeleteSnapshotPolicy(policyId string) error { return cloudprovider.ErrNotImplemented } diff --git a/pkg/multicloud/ctyun/securitygroup.go b/pkg/multicloud/ctyun/securitygroup.go index 0038d5801e..fd76efe7af 100644 --- a/pkg/multicloud/ctyun/securitygroup.go +++ b/pkg/multicloud/ctyun/securitygroup.go @@ -15,20 +15,21 @@ package ctyun import ( + "fmt" "net" + "sort" + "strings" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/secrules" - - "yunion.io/x/onecloud/pkg/cloudprovider" ) type SSecurityGroup struct { region *SRegion vpc *SVpc - ID string `json:"id"` ResSecurityGroupID string `json:"resSecurityGroupId"` Name string `json:"name"` AccountID string `json:"accountId"` @@ -40,12 +41,179 @@ type SSecurityGroup struct { Status int64 `json:"status"` } +// 将安全组规则全部转换为等价的allow规则 +func SecurityRuleSetToAllowSet(srs secrules.SecurityRuleSet) secrules.SecurityRuleSet { + inRuleSet := secrules.SecurityRuleSet{} + outRuleSet := secrules.SecurityRuleSet{} + + for _, rule := range srs { + if rule.Direction == secrules.SecurityRuleIngress { + inRuleSet = append(inRuleSet, rule) + } + + if rule.Direction == secrules.SecurityRuleEgress { + outRuleSet = append(outRuleSet, rule) + } + } + + sort.Sort(inRuleSet) + sort.Sort(outRuleSet) + + inRuleSet = inRuleSet.AllowList() + // out方向空规则默认全部放行 + if outRuleSet.Len() == 0 { + _, ipNet, _ := net.ParseCIDR("0.0.0.0/0") + outRuleSet = append(outRuleSet, secrules.SecurityRule{ + Priority: 0, + Action: secrules.SecurityRuleAllow, + IPNet: ipNet, + Protocol: secrules.PROTO_ANY, + Direction: secrules.SecurityRuleEgress, + PortStart: -1, + PortEnd: -1, + }) + } + outRuleSet = outRuleSet.AllowList() + + ret := secrules.SecurityRuleSet{} + ret = append(ret, inRuleSet...) + ret = append(ret, outRuleSet...) + return ret +} + +func (self *SSecurityGroup) GetRulesWithExtId() ([]secrules.SecurityRule, error) { + _rules, err := self.region.GetSecurityGroupRules(self.GetId()) + if err != nil { + return nil, errors.Wrap(err, "SSecurityGroup.GetRulesWithExtId.GetSecurityGroupRules") + } + + rules := make([]secrules.SecurityRule, 0) + for _, r := range _rules { + if !compatibleSecurityGroupRule(r) { + continue + } + + rule, err := self.GetSecurityRule(r, true) + if err != nil { + return rules, err + } + + rules = append(rules, rule) + } + + return rules, nil +} + +func (self *SRegion) syncSecgroupRules(secgroupId string, rules []secrules.SecurityRule) error { + var DeleteRules []secrules.SecurityRule + var AddRules []secrules.SecurityRule + + if secgroup, err := self.GetSecurityGroupDetails(secgroupId); err != nil { + return errors.Wrapf(err, "syncSecgroupRules.GetSecurityGroupDetails(%s)", secgroupId) + } else { + remoteRules, err := secgroup.GetRulesWithExtId() + if err != nil { + return errors.Wrap(err, "secgroup.GetRulesWithExtId") + } + + sort.Sort(secrules.SecurityRuleSet(rules)) + sort.Sort(secrules.SecurityRuleSet(remoteRules)) + + i, j := 0, 0 + for i < len(rules) || j < len(remoteRules) { + if i < len(rules) && j < len(remoteRules) { + permissionStr := remoteRules[j].String() + ruleStr := rules[i].String() + cmp := strings.Compare(permissionStr, ruleStr) + if cmp == 0 { + // DeleteRules = append(DeleteRules, remoteRules[j]) + // AddRules = append(AddRules, rules[i]) + i += 1 + j += 1 + } else if cmp > 0 { + DeleteRules = append(DeleteRules, remoteRules[j]) + j += 1 + } else { + AddRules = append(AddRules, rules[i]) + i += 1 + } + } else if i >= len(rules) { + DeleteRules = append(DeleteRules, remoteRules[j]) + j += 1 + } else if j >= len(remoteRules) { + AddRules = append(AddRules, rules[i]) + i += 1 + } + } + } + + for _, r := range DeleteRules { + // r.Description 实际存储的是ruleId + if err := self.delSecurityGroupRule(r.Description); err != nil { + log.Errorf("delSecurityGroupRule %v error: %s", r, err.Error()) + return err + } + } + + for _, r := range AddRules { + if err := self.addSecurityGroupRules(secgroupId, &r); err != nil { + log.Errorf("addSecurityGroupRule %v error: %s", r, err.Error()) + return err + } + } + + return nil +} + +func (self *SRegion) delSecurityGroupRule(secGrpRuleId string) error { + return self.DeleteSecurityGroupRule(secGrpRuleId) +} + +func (self *SRegion) addSecurityGroupRules(secGrpId string, rule *secrules.SecurityRule) error { + direction := "" + if rule.Direction == secrules.SecurityRuleIngress { + direction = "ingress" + } else { + direction = "egress" + } + + protocal := rule.Protocol + if rule.Protocol == secrules.PROTO_ANY { + protocal = "" + } + + // imcp协议默认为any + if rule.Protocol == secrules.PROTO_ICMP { + return self.addSecurityGroupRule(secGrpId, direction, "-1", "-1", protocal, rule.IPNet.String()) + } + + if len(rule.Ports) > 0 { + for _, port := range rule.Ports { + portStr := fmt.Sprintf("%d", port) + err := self.addSecurityGroupRule(secGrpId, direction, portStr, portStr, protocal, rule.IPNet.String()) + if err != nil { + return err + } + } + } else { + portStart := fmt.Sprintf("%d", rule.PortStart) + portEnd := fmt.Sprintf("%d", rule.PortEnd) + err := self.addSecurityGroupRule(secGrpId, direction, portStart, portEnd, protocal, rule.IPNet.String()) + if err != nil { + return err + } + } + + return nil +} + func (self *SSecurityGroup) SyncRules(rules []secrules.SecurityRule) error { - return cloudprovider.ErrNotImplemented + rules = SecurityRuleSetToAllowSet(rules) + return self.region.syncSecgroupRules(self.ResSecurityGroupID, rules) } func (self *SSecurityGroup) Delete() error { - return cloudprovider.ErrNotImplemented + return self.region.DeleteSecurityGroup(self.GetId()) } func (self *SSecurityGroup) GetId() string { @@ -252,10 +420,14 @@ func (self *SRegion) CreateSecurityGroup(vpcId, name string) (*SSecurityGroup, e return nil, errors.Wrap(err, "SRegion.CreateSecurityGroup.DoPost") } - ret := &SSecurityGroup{} - err = resp.Unmarshal(ret, "returnObj") + secgroupId, err := resp.GetString("returnObj", "id") if err != nil { - return nil, errors.Wrap(err, "SRegion.CreateSecurityGroup.Unmarshal") + return nil, errors.Wrap(err, "SRegion.CreateSecurityGroup.GetSecgroupId") + } + + secgroup, err := self.GetSecurityGroupDetails(secgroupId) + if err != nil { + return nil, errors.Wrap(err, "SRegion.CreateSecurityGroup.GetISecurityGroupById") } vpc, err := self.GetVpc(vpcId) @@ -263,7 +435,57 @@ func (self *SRegion) CreateSecurityGroup(vpcId, name string) (*SSecurityGroup, e return nil, errors.Wrap(err, "SRegion.CreateSecurityGroup.GetVpc") } - ret.vpc = vpc - ret.region = self - return ret, nil + secgroup.vpc = vpc + secgroup.region = self + return secgroup, nil +} + +func (self *SRegion) DeleteSecurityGroupRule(securityGroupRuleId string) error { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "securityGroupRuleId": jsonutils.NewString(securityGroupRuleId), + } + + _, err := self.client.DoPost("/apiproxy/v3/deleteSecurityGroupRule", params) + if err != nil { + return errors.Wrap(err, "SRegion.DeleteSecurityGroupRule.DoPost") + } + + return err +} + +func (self *SRegion) addSecurityGroupRule(secGrpId, direction, portStart, portEnd, protocol, ipNet string) error { + secgroupObj := jsonutils.NewDict() + secgroupObj.Add(jsonutils.NewString(self.GetId()), "regionId") + secgroupObj.Add(jsonutils.NewString(secGrpId), "securityGroupId") + secgroupObj.Add(jsonutils.NewString(direction), "direction") + secgroupObj.Add(jsonutils.NewString(ipNet), "remoteIpPrefix") + secgroupObj.Add(jsonutils.NewString("IPv4"), "ethertype") + // 端口为空或者1-65535 + if len(portStart) > 0 && portStart != "0" && portStart != "-1" { + secgroupObj.Add(jsonutils.NewString(portStart), "portRangeMin") + } + if len(portEnd) > 0 && portEnd != "0" && portEnd != "-1" { + secgroupObj.Add(jsonutils.NewString(portEnd), "portRangeMax") + } + if len(protocol) > 0 { + secgroupObj.Add(jsonutils.NewString(protocol), "protocol") + } + + params := map[string]jsonutils.JSONObject{ + "jsonStr": secgroupObj, + } + + resp, err := self.client.DoPost("/apiproxy/v3/createSecurityGroupRule", params) + if err != nil { + return errors.Wrap(err, "SRegion.DoPost") + } + + rule := SSecurityGroupRule{} + err = resp.Unmarshal(&rule, "returnObj") + if err != nil { + return errors.Wrap(err, "SRegion.Unmarshal") + } + + return nil } diff --git a/pkg/multicloud/ctyun/shell/eip.go b/pkg/multicloud/ctyun/shell/eip.go index da39b3c21b..7bd41a1e03 100644 --- a/pkg/multicloud/ctyun/shell/eip.go +++ b/pkg/multicloud/ctyun/shell/eip.go @@ -32,13 +32,14 @@ func init() { }) type EipCreateOptions struct { - ZoneId string `help:"zone id"` - Name string `help:"eip name"` - Size string `help:"size"` - ShareType string `help:"share type" choice:"PER|WHOLE"` + ZoneId string `help:"zone id"` + Name string `help:"eip name"` + Size string `help:"size"` + ShareType string `help:"share type" choice:"PER|WHOLE"` + ChargeMode string `help:"charge mode" choice:"bandwidth|traffic"` } shellutils.R(&EipCreateOptions{}, "eip-create", "Create eip", func(cli *ctyun.SRegion, args *EipCreateOptions) error { - eip, e := cli.CreateEip(args.ZoneId, args.Name, args.Size, args.ShareType) + eip, e := cli.CreateEip(args.ZoneId, args.Name, args.Size, args.ShareType, args.ChargeMode) if e != nil { return e } diff --git a/pkg/multicloud/ctyun/shell/instance.go b/pkg/multicloud/ctyun/shell/instance.go index 4296b6fa4b..814fb4b222 100644 --- a/pkg/multicloud/ctyun/shell/instance.go +++ b/pkg/multicloud/ctyun/shell/instance.go @@ -37,6 +37,7 @@ func init() { NAME string `help:"name of instance"` ADMINPASS string `help:"admin password of instance"` ImageId string `help:"image Id of instance"` + OsType string `help:"Os type of image"` VolumeType string `help:"volume type of instance"` Flavor string `help:"Flavor of instance"` VpcId string `help:"Vpc of instance"` @@ -45,7 +46,7 @@ func init() { } shellutils.R(&InstanceCreateOptions{}, "instance-create", "Create intance", func(cli *ctyun.SRegion, args *InstanceCreateOptions) error { - e := cli.CreateInstance(args.ZoneId, args.NAME, args.ImageId, args.VolumeType, args.Flavor, args.VpcId, args.SubnetId, args.SecGroupId, args.ADMINPASS) + _, e := cli.CreateInstance(args.ZoneId, args.NAME, args.ImageId, args.OsType, args.VolumeType, args.Flavor, args.VpcId, args.SubnetId, args.SecGroupId, args.ADMINPASS) if e != nil { return e } diff --git a/pkg/multicloud/ctyun/shell/network.go b/pkg/multicloud/ctyun/shell/network.go index a9f6408e7f..e2b7460eef 100644 --- a/pkg/multicloud/ctyun/shell/network.go +++ b/pkg/multicloud/ctyun/shell/network.go @@ -41,7 +41,7 @@ func init() { DhcpEnable string `help:"gateway ip" choice:"true|false"` } shellutils.R(&NetworkCreateOptions{}, "subnet-create", "Create subnet", func(cli *ctyun.SRegion, args *NetworkCreateOptions) error { - vpc, e := cli.CreateNetwork(args.VpcId, args.ZoneId, args.Name, args.Cidr, args.GatewayIp, args.DhcpEnable) + vpc, e := cli.CreateNetwork(args.VpcId, args.ZoneId, args.Name, args.Cidr, args.DhcpEnable) if e != nil { return e } diff --git a/pkg/multicloud/ctyun/snapshot.go b/pkg/multicloud/ctyun/snapshot.go index 70a35a0c34..d6c3f0ffad 100644 --- a/pkg/multicloud/ctyun/snapshot.go +++ b/pkg/multicloud/ctyun/snapshot.go @@ -15,12 +15,13 @@ package ctyun import ( + "fmt" + "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" api "yunion.io/x/onecloud/pkg/apis/compute" - "yunion.io/x/onecloud/pkg/cloudprovider" ) type SSnapshot struct { @@ -111,7 +112,12 @@ func (self *SSnapshot) GetDiskType() string { } func (self *SSnapshot) Delete() error { - return cloudprovider.ErrNotImplemented + _, err := self.region.DeleteSnapshot(self.GetId()) + if err != nil { + return errors.Wrap(err, "Snapshot.Delete.DeleteSnapshot") + } + + return nil } func (self *SRegion) GetSnapshot(diskId string, snapshotId string) (*SSnapshot, error) { @@ -157,3 +163,30 @@ func (self *SRegion) GetSnapshots(diskId string) ([]SSnapshot, error) { return ret, nil } + +func (self *SRegion) DeleteSnapshot(vbsId string) (string, error) { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "vbsId": jsonutils.NewString(vbsId), + } + + resp, err := self.client.DoPost("/apiproxy/v3/ondemand/deleteVBS", params) + if err != nil { + return "", errors.Wrap(err, "SRegion.DeleteSnapshot.DoPost") + } + + var ok bool + err = resp.Unmarshal(&ok, "returnObj", "status") + if !ok { + msg, _ := resp.GetString("message") + return "", fmt.Errorf("SRegion.DeleteSnapshot.JobFailed %s", msg) + } + + var jobId string + err = resp.Unmarshal(&jobId, "returnObj", "data") + if err != nil { + return "", errors.Wrap(err, "SRegion.DeleteSnapshot.Unmarshal") + } + + return jobId, nil +} diff --git a/pkg/multicloud/ctyun/storage.go b/pkg/multicloud/ctyun/storage.go index 6bc9487eb8..f008e38e5c 100644 --- a/pkg/multicloud/ctyun/storage.go +++ b/pkg/multicloud/ctyun/storage.go @@ -16,9 +16,11 @@ package ctyun import ( "fmt" + "strconv" "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/errors" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudprovider" @@ -121,7 +123,12 @@ func (self *SStorage) GetEnabled() bool { } func (self *SStorage) CreateIDisk(name string, sizeGb int, desc string) (cloudprovider.ICloudDisk, error) { - return nil, cloudprovider.ErrNotImplemented + disk, err := self.zone.region.CreateDisk(self.zone.GetId(), self.GetName(), self.GetStorageType(), strconv.Itoa(sizeGb)) + if err != nil { + return nil, errors.Wrap(err, "Storage.CreateIDisk.CreateDisk") + } + + return disk, nil } func (self *SStorage) GetIDiskById(idStr string) (cloudprovider.ICloudDisk, error) { diff --git a/pkg/multicloud/ctyun/storagecache.go b/pkg/multicloud/ctyun/storagecache.go index 090ee1b9b3..3264d1a1e1 100644 --- a/pkg/multicloud/ctyun/storagecache.go +++ b/pkg/multicloud/ctyun/storagecache.go @@ -33,15 +33,15 @@ type SStoragecache struct { } func (self *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) { - return nil, cloudprovider.ErrNotImplemented + return nil, cloudprovider.ErrNotSupported } func (self *SStoragecache) DownloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) { - return nil, cloudprovider.ErrNotImplemented + return nil, cloudprovider.ErrNotSupported } func (self *SStoragecache) UploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, isForce bool) (string, error) { - return "", cloudprovider.ErrNotImplemented + return "", cloudprovider.ErrNotSupported } func GetBucketName(regionId string, imageId string) string { diff --git a/pkg/multicloud/ctyun/vpc.go b/pkg/multicloud/ctyun/vpc.go index 06a8c24bd2..c0aaab847e 100644 --- a/pkg/multicloud/ctyun/vpc.go +++ b/pkg/multicloud/ctyun/vpc.go @@ -123,7 +123,7 @@ func (self *SVpc) GetIRouteTables() ([]cloudprovider.ICloudRouteTable, error) { } func (self *SVpc) Delete() error { - return cloudprovider.ErrNotImplemented + return self.region.DeleteVpc(self.GetId()) } func (self *SVpc) GetIWireById(wireId string) (cloudprovider.ICloudWire, error) { @@ -212,3 +212,17 @@ func (self *SRegion) GetVpc(vpcId string) (*SVpc, error) { vpc.region = self return vpc, nil } + +func (self *SRegion) DeleteVpc(vpcId string) error { + params := map[string]jsonutils.JSONObject{ + "regionId": jsonutils.NewString(self.GetId()), + "vpcId": jsonutils.NewString(vpcId), + } + + _, err := self.client.DoPost("/apiproxy/v3/deleteVPC", params) + if err != nil { + return errors.Wrap(err, "SRegion.DeleteVpc.DoPost") + } + + return nil +} diff --git a/pkg/multicloud/ctyun/wire.go b/pkg/multicloud/ctyun/wire.go index ed828baae9..d6a95dfa38 100644 --- a/pkg/multicloud/ctyun/wire.go +++ b/pkg/multicloud/ctyun/wire.go @@ -107,7 +107,12 @@ func getDefaultGateWay(cidr string) (string, error) { } func (self *SWire) CreateINetwork(name string, cidr string, desc string) (cloudprovider.ICloudNetwork, error) { - return nil, cloudprovider.ErrNotImplemented + network, err := self.region.CreateNetwork(self.vpc.GetId(), self.inetworks[0].(*SNetwork).ZoneID, name, cidr, "true") + if err != nil { + return nil, errors.Wrap(err, "SWire.CreateINetwork.CreateNetwork") + } + + return network, nil } func (self *SWire) addNetwork(network *SNetwork) { diff --git a/pkg/webconsole/handlers.go b/pkg/webconsole/handlers.go index 3afbb7e5d0..4448c2b660 100644 --- a/pkg/webconsole/handlers.go +++ b/pkg/webconsole/handlers.go @@ -203,7 +203,7 @@ func handleServerRemoteConsole(ctx context.Context, w http.ResponseWriter, r *ht return } switch info.Protocol { - case session.ALIYUN, session.QCLOUD, session.OPENSTACK, session.VMRC, session.ZSTACK: + case session.ALIYUN, session.QCLOUD, session.OPENSTACK, session.VMRC, session.ZSTACK, session.CTYUN: responsePublicCloudConsole(info, w) case session.VNC, session.SPICE, session.WMKS: handleDataSession(info, w, url.Values{"password": {info.GetPassword()}}) diff --git a/pkg/webconsole/session/remote_console.go b/pkg/webconsole/session/remote_console.go index bcfcc4bbab..59638b005a 100644 --- a/pkg/webconsole/session/remote_console.go +++ b/pkg/webconsole/session/remote_console.go @@ -32,6 +32,7 @@ const ( WMKS = "wmks" VMRC = "vmrc" ZSTACK = "zstack" + CTYUN = "ctyun" ) type RemoteConsoleInfo struct { @@ -109,7 +110,7 @@ func (info *RemoteConsoleInfo) GetConnectParams() (string, error) { return info.getAliyunURL() case QCLOUD: return info.getQcloudURL() - case OPENSTACK, VMRC, ZSTACK: + case OPENSTACK, VMRC, ZSTACK, CTYUN: return info.Url, nil default: return "", fmt.Errorf("Can't convert protocol %s to connect params", info.Protocol)