mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
Merge pull request #1048 in YUNIONIO/onecloud from ~TANGBIN/onecloud:bugfix/tb-huawei-stage-2.2-fix to release/2.6.0
* commit 'fa49e138f26fbeb21a193ef48befb98dcbe37f99': update vendor & bugfix bugfix 替换不必要的重复func remove useless todo rebuild root bugfix server get disks fix server delete etc fix server delete etc fix
This commit is contained in:
Generated
+3
-2
@@ -1676,7 +1676,7 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:6277045f9c9a63d84e1aa48c7ad3c1f42ea6523bc54ea92e772d541c78e37404"
|
||||
digest = "1:37d4e9b3030c077522fc5669b497a25ce53429da77d5e592226658e57cc604bb"
|
||||
name = "yunion.io/x/pkg"
|
||||
packages = [
|
||||
"gotypes",
|
||||
@@ -1710,7 +1710,7 @@
|
||||
"utils",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "e2e42205d868c26829b9322b40cdc48f24fed379"
|
||||
revision = "5fbe7b39e5f599aa49f6f597d57da460d93c26ff"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@@ -1826,6 +1826,7 @@
|
||||
"golang.org/x/net/ipv4",
|
||||
"golang.org/x/sys/unix",
|
||||
"gopkg.in/gin-gonic/gin.v1",
|
||||
"gopkg.in/yaml.v2",
|
||||
"k8s.io/api/core/v1",
|
||||
"k8s.io/apimachinery/pkg/api/errors",
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
|
||||
@@ -192,11 +192,11 @@ func (self *SHuaweiGuestDriver) RequestDeployGuestOnHost(ctx context.Context, gu
|
||||
|
||||
log.Debugf("VMrebuildRoot %s new diskID %s, wait status ready ...", iVM.GetGlobalId(), diskId)
|
||||
|
||||
err = cloudprovider.WaitStatus(iVM, models.VM_READY, time.Second*5, time.Second*1800)
|
||||
err = cloudprovider.WaitStatus(iVM, models.VM_RUNNING, time.Second*5, time.Second*1800)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("VMrebuildRoot %s, and status is ready", iVM.GetGlobalId())
|
||||
log.Debugf("VMrebuildRoot %s, and status is %s", iVM.GetGlobalId(), iVM.GetStatus())
|
||||
|
||||
maxWaitSecs := 300
|
||||
waited := 0
|
||||
|
||||
@@ -147,6 +147,12 @@ func (self *SManagedVirtualizedGuestDriver) RequestUndeployGuestOnHost(ctx conte
|
||||
log.Errorf("host.GetIHost fail %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建失败时external id为空。此时直接返回即可。不需要再调用公有云api
|
||||
if len(guest.ExternalId) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ivm, err := ihost.GetIVMById(guest.ExternalId)
|
||||
if err != nil {
|
||||
if err == cloudprovider.ErrNotFound {
|
||||
|
||||
@@ -52,11 +52,7 @@ func (self *SOrderManager) SetDomainId(domainId string) error {
|
||||
|
||||
// 查询客户包周期资源列表 https://support.huaweicloud.com/api-oce/zh-cn_topic_0084961226.html
|
||||
func (self *SOrderManager) List(querys map[string]string) (*responses.ListResult, error) {
|
||||
if self.orderCtx == nil {
|
||||
return nil, fmt.Errorf("domainId is emtpy.Use SetDomainId method to set.")
|
||||
}
|
||||
|
||||
return self.ListInContextWithSpec(self.orderCtx, "resources/detail", querys, "data")
|
||||
return nil, fmt.Errorf("Not Suppport List Order")
|
||||
}
|
||||
|
||||
// 查询订单的资源开通详情 https://support.huaweicloud.com/api-oce/api_order_00001.html
|
||||
@@ -75,3 +71,11 @@ func (self *SOrderManager) PerformAction(action string, id string, params jsonut
|
||||
|
||||
return self._do(request, "")
|
||||
}
|
||||
|
||||
func (self *SOrderManager) GetPeriodResourceList(querys map[string]string) (*responses.ListResult, error) {
|
||||
if self.orderCtx == nil {
|
||||
return nil, fmt.Errorf("domainId is emtpy.Use SetDomainId method to set.")
|
||||
}
|
||||
|
||||
return self.ListInContextWithSpec(self.orderCtx, "resources/detail", querys, "data")
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ package huawei
|
||||
const DATETIME_FORMAT = "2006-01-02T15:04:05.999999999"
|
||||
|
||||
// Task status
|
||||
const TASK_SUCCESS = "SUCCESS"
|
||||
const (
|
||||
TASK_SUCCESS = "SUCCESS"
|
||||
TASK_FAIL = "FAIL"
|
||||
)
|
||||
|
||||
// Charging Type
|
||||
const (
|
||||
@@ -20,3 +23,35 @@ const (
|
||||
RESOURCE_TYPE_IP = "hws.resource.type.ip" // VPC公网IP
|
||||
RESOURCE_TYPE_IMAGE = "hws.resource.type.marketplace" // 市场镜像
|
||||
)
|
||||
|
||||
// Not Found Error code
|
||||
// 网络等资源直接通过http code 404即可判断资源不存在。另外有些资源可能不是返回404这里单独列出来
|
||||
const (
|
||||
VM_NOT_FOUND = "Ecs.0114" // 云服务器不存在
|
||||
ECS_NOT_FOUND = "Ecs.0614" // 弹性云服务器不存在
|
||||
IMG_ID_NOT_FOUND = "IMG.0027" // 请求的镜像ID不存在
|
||||
IMG_NOT_FOUND = "IMG.0027" // 镜像不存在
|
||||
IMG_ERR_NOT_FOUND = "IMG.0057" // 镜像文件不存在或者为空或者不是允许格式的文件
|
||||
IMG_BACKUP_NOT_FOUND = "IMG.0020" // 备份不存在
|
||||
IMG_VM_BACKUP_NOT_FOUND = "IMG.0127" // 云服务器备份不存在
|
||||
IMG_VM_NOT_FOUND = "IMG.0005" // 云主机不存在
|
||||
JOB_NOT_FOUND = "Common.0011" // jobId为空
|
||||
EVS_NOT_FOUND = "EVS.5404" // 磁盘、快照和备份等资源未找到。
|
||||
FIP_NOT_FOUND = "VPC.0504" // 未找到弹性公网IP。
|
||||
VPC_NOT_FOUND = "VPC.0012" // 未找到弹性公网VPC。
|
||||
)
|
||||
|
||||
var NOT_FOUND_CODES = []string{
|
||||
VM_NOT_FOUND,
|
||||
ECS_NOT_FOUND,
|
||||
IMG_ID_NOT_FOUND,
|
||||
IMG_NOT_FOUND,
|
||||
IMG_ERR_NOT_FOUND,
|
||||
IMG_BACKUP_NOT_FOUND,
|
||||
IMG_VM_BACKUP_NOT_FOUND,
|
||||
IMG_VM_NOT_FOUND,
|
||||
JOB_NOT_FOUND,
|
||||
EVS_NOT_FOUND,
|
||||
FIP_NOT_FOUND,
|
||||
VPC_NOT_FOUND,
|
||||
}
|
||||
|
||||
+58
-1
@@ -233,6 +233,8 @@ func (self *SDisk) GetTemplateId() string {
|
||||
return self.VolumeImageMetadata.ImageID
|
||||
}
|
||||
|
||||
// Bootable 表示硬盘是否为启动盘。
|
||||
// 启动盘 != 系统盘(必须是启动盘且挂载在root device上)
|
||||
func (self *SDisk) GetDiskType() string {
|
||||
if self.Bootable == "true" {
|
||||
return models.DISK_TYPE_SYS
|
||||
@@ -268,6 +270,14 @@ func (self *SDisk) GetMountpoint() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *SDisk) GetMountServerId() string {
|
||||
if len(self.Attachments) > 0 {
|
||||
return self.Attachments[0].ServerID
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *SDisk) GetAccessPath() string {
|
||||
return ""
|
||||
}
|
||||
@@ -323,8 +333,55 @@ func (self *SDisk) Resize(ctx context.Context, newSizeMB int64) error {
|
||||
return self.storage.zone.region.resizeDisk(self.GetId(), sizeGb)
|
||||
}
|
||||
|
||||
func (self *SDisk) Detach() error {
|
||||
err := self.storage.zone.region.DetachDisk(self.GetMountServerId(), self.GetId())
|
||||
if err != nil {
|
||||
log.Debugf("detach server %s disk %s failed: %s", self.GetMountServerId(), self.GetId(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
return cloudprovider.WaitStatus(self, models.DISK_READY, 5*time.Second, 60*time.Second)
|
||||
}
|
||||
|
||||
func (self *SDisk) Attach(device string) error {
|
||||
err := self.storage.zone.region.AttachDisk(self.GetMountServerId(), self.GetId(), device)
|
||||
if err != nil {
|
||||
log.Debugf("attach server %s disk %s failed: %s", self.GetMountServerId(), self.GetId(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
return cloudprovider.WaitStatus(self, models.DISK_READY, 5*time.Second, 60*time.Second)
|
||||
}
|
||||
|
||||
// 在线卸载磁盘 https://support.huaweicloud.com/usermanual-ecs/zh-cn_topic_0036046828.html
|
||||
// 对于挂载在系统盘盘位(也就是“/dev/sda”或“/dev/vda”挂载点)上的磁盘,当前仅支持离线卸载
|
||||
func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) {
|
||||
return self.storage.zone.region.resetDisk(self.GetId(), snapshotId)
|
||||
mountpoint := self.GetMountpoint()
|
||||
if mountpoint == "/dev/sda" || mountpoint == "/dev/vda" {
|
||||
err := self.Detach()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
diskId, err := self.storage.zone.region.resetDisk(self.GetId(), snapshotId)
|
||||
if err != nil {
|
||||
return diskId, err
|
||||
}
|
||||
|
||||
err = cloudprovider.WaitStatus(self, models.DISK_READY, 5*time.Second, 300*time.Second)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if mountpoint == "/dev/sda" || mountpoint == "/dev/vda" {
|
||||
err := self.Attach(mountpoint)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return diskId, nil
|
||||
}
|
||||
|
||||
// 华为云不支持重置
|
||||
|
||||
+110
-28
@@ -91,12 +91,11 @@ type SInstance struct {
|
||||
Progress string `json:"progress"`
|
||||
HostID string `json:"hostId"`
|
||||
Updated string `json:"updated"`
|
||||
Created string `json:"created"`
|
||||
Created time.Time `json:"created"`
|
||||
Metadata VMMetadata `json:"metadata"`
|
||||
Tags []string `json:"tags"`
|
||||
Description string `json:"description"`
|
||||
Locked bool `json:"locked"`
|
||||
Image Image `json:"image"`
|
||||
ConfigDrive string `json:"config_drive"`
|
||||
TenantID string `json:"tenant_id"`
|
||||
UserID string `json:"user_id"`
|
||||
@@ -115,13 +114,13 @@ type SInstance struct {
|
||||
OSEXTSRVATTRRamdiskID string `json:"OS-EXT-SRV-ATTR:ramdisk_id"`
|
||||
EnterpriseProjectID string `json:"enterprise_project_id"`
|
||||
OSEXTSRVATTRUserData string `json:"OS-EXT-SRV-ATTR:user_data"`
|
||||
OSSRVUSGLaunchedAt string `json:"OS-SRV-USG:launched_at"`
|
||||
OSSRVUSGLaunchedAt time.Time `json:"OS-SRV-USG:launched_at"`
|
||||
OSEXTSRVATTRKernelID string `json:"OS-EXT-SRV-ATTR:kernel_id"`
|
||||
OSEXTSRVATTRLaunchIndex int64 `json:"OS-EXT-SRV-ATTR:launch_index"`
|
||||
HostStatus string `json:"host_status"`
|
||||
OSEXTSRVATTRReservationID string `json:"OS-EXT-SRV-ATTR:reservation_id"`
|
||||
OSEXTSRVATTRHostname string `json:"OS-EXT-SRV-ATTR:hostname"`
|
||||
OSSRVUSGTerminatedAt string `json:"OS-SRV-USG:terminated_at"`
|
||||
OSSRVUSGTerminatedAt time.Time `json:"OS-SRV-USG:terminated_at"`
|
||||
SysTags []SysTag `json:"sys_tags"`
|
||||
SecurityGroups []SecurityGroup `json:"security_groups"`
|
||||
}
|
||||
@@ -156,6 +155,21 @@ func compareSet(currentSet []string, newSet []string) (add []string, remove []st
|
||||
return add, remove, keep
|
||||
}
|
||||
|
||||
// 启动盘 != 系统盘(必须是启动盘且挂载在root device上)
|
||||
func isBootDisk(server *SInstance, disk *SDisk) bool {
|
||||
if disk.GetDiskType() != models.DISK_TYPE_SYS {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, attachment := range disk.Attachments {
|
||||
if attachment.ServerID == server.GetId() && attachment.Device == server.OSEXTSRVATTRRootDeviceName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *SInstance) GetId() string {
|
||||
return self.ID
|
||||
}
|
||||
@@ -189,6 +203,12 @@ func (self *SInstance) Refresh() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if new.Status == InstanceStatusTerminated {
|
||||
log.Debugf("Instance already terminated.")
|
||||
return cloudprovider.ErrNotFound
|
||||
}
|
||||
|
||||
return jsonutils.Update(self, new)
|
||||
}
|
||||
|
||||
@@ -210,9 +230,9 @@ func (self *SInstance) GetMetadata() *jsonutils.JSONDict {
|
||||
priceKey := fmt.Sprintf("%s::%s::%s", self.host.zone.region.GetId(), self.GetInstanceType(), lowerOs)
|
||||
data.Add(jsonutils.NewString(priceKey), "price_key")
|
||||
data.Add(jsonutils.NewString(self.host.zone.GetGlobalId()), "zone_ext_id")
|
||||
if len(self.Image.ID) > 0 {
|
||||
if image, err := self.host.zone.region.GetImage(self.Image.ID); err != nil {
|
||||
log.Errorf("Failed to find image %s for instance %s zone %s", self.Image.ID, self.GetId(), self.OSEXTAZAvailabilityZone)
|
||||
if len(self.Metadata.MeteringImageID) > 0 {
|
||||
if image, err := self.host.zone.region.GetImage(self.Metadata.MeteringImageID); err != nil {
|
||||
log.Errorf("Failed to find image %s for instance %s zone %s", self.Metadata.MeteringImageID, self.GetId(), self.OSEXTAZAvailabilityZone)
|
||||
} else if meta := image.GetMetadata(); meta != nil {
|
||||
data.Update(meta)
|
||||
}
|
||||
@@ -241,14 +261,23 @@ func (self *SInstance) GetBillingType() string {
|
||||
}
|
||||
}
|
||||
|
||||
// charging_mode “0”:按需计费 “1”:按包年包月计费
|
||||
func (self *SInstance) GetExpiredAt() time.Time {
|
||||
t, _ := time.Parse(DATETIME_FORMAT, self.OSSRVUSGTerminatedAt)
|
||||
return t
|
||||
var expiredTime time.Time
|
||||
if self.Metadata.ChargingMode == "1" {
|
||||
res, err := self.host.zone.region.GetOrderResourceDetail(self.GetId())
|
||||
if err != nil {
|
||||
log.Debugf(err.Error())
|
||||
}
|
||||
|
||||
expiredTime = res.ExpireTime
|
||||
}
|
||||
|
||||
return expiredTime
|
||||
}
|
||||
|
||||
func (self *SInstance) GetCreateTime() time.Time {
|
||||
t, _ := time.Parse(DATETIME_FORMAT, self.Created)
|
||||
return t
|
||||
return self.Created
|
||||
}
|
||||
|
||||
func (self *SInstance) GetIHost() cloudprovider.ICloudHost {
|
||||
@@ -275,6 +304,12 @@ func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
|
||||
}
|
||||
disks[i].storage = storage
|
||||
idisks[i] = &disks[i]
|
||||
// 将系统盘放到第0个位置
|
||||
if isBootDisk(self, &disks[i]) {
|
||||
_temp := idisks[0]
|
||||
idisks[0] = &disks[i]
|
||||
idisks[i] = _temp
|
||||
}
|
||||
}
|
||||
return idisks, nil
|
||||
}
|
||||
@@ -418,6 +453,11 @@ func (self *SInstance) StopVM(ctx context.Context, isForce bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if self.Status == InstanceStatusTerminated {
|
||||
log.Debugf("Instance already terminated.")
|
||||
return nil
|
||||
}
|
||||
|
||||
err := self.host.zone.region.StopVM(self.GetId(), isForce)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -426,6 +466,10 @@ func (self *SInstance) StopVM(ctx context.Context, isForce bool) error {
|
||||
}
|
||||
|
||||
func (self *SInstance) DeleteVM(ctx context.Context) error {
|
||||
if self.Status == InstanceStatusTerminated {
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
err := self.host.zone.region.DeleteVM(self.GetId())
|
||||
if err != nil && self.Status != InstanceStatusTerminated {
|
||||
@@ -457,20 +501,40 @@ func (self *SInstance) UpdateUserData(userData string) error {
|
||||
// todo: 支持注入user_data
|
||||
func (self *SInstance) RebuildRoot(ctx context.Context, imageId string, passwd string, publicKey string, sysSizeGB int) (string, error) {
|
||||
var err error
|
||||
if self.Image.ID == imageId {
|
||||
err = self.host.zone.region.RebuildRoot(ctx, self.GetId(), passwd, publicKey)
|
||||
var jobId string
|
||||
if self.Metadata.MeteringImageID == imageId {
|
||||
jobId, err = self.host.zone.region.RebuildRoot(ctx, self.GetId(), passwd, publicKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
err = self.host.zone.region.ChangeRoot(ctx, self.GetId(), imageId, passwd, publicKey)
|
||||
jobId, err = self.host.zone.region.ChangeRoot(ctx, self.GetId(), imageId, passwd, publicKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// todo: wait job finished here
|
||||
return "", nil
|
||||
err = self.host.zone.region.waitTaskStatus(self.host.zone.region.ecsClient.Servers.ServiceType(), jobId, TASK_SUCCESS, 15*time.Second, 900*time.Second)
|
||||
if err != nil {
|
||||
log.Errorf("RebuildRoot task error %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
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, password string, publicKey string, deleteKeypair bool, description string) error {
|
||||
@@ -670,6 +734,16 @@ func (self *SRegion) CreateInstance(name string, imageId string, instanceType st
|
||||
} else {
|
||||
// 包年包月
|
||||
err = cloudprovider.WaitCreated(10*time.Second, 180*time.Second, func() bool {
|
||||
order, e := self.GetOrder(_id)
|
||||
if e != nil {
|
||||
log.Debugf(err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
if order.TotalSize == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
ids, err = self.getAllResIdsByType(_id, RESOURCE_TYPE_VM)
|
||||
if err != nil {
|
||||
log.Debugf(err.Error())
|
||||
@@ -827,10 +901,6 @@ func (self *SRegion) DeleteVM(instanceId string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if remoteStatus == InstanceStatusTerminated {
|
||||
return nil
|
||||
}
|
||||
|
||||
if remoteStatus != InstanceStatusStopped {
|
||||
log.Errorf("DeleteVM vm status is %s expect %s", remoteStatus, InstanceStatusStopped)
|
||||
return cloudprovider.ErrInvalidStatus
|
||||
@@ -862,7 +932,9 @@ func (self *SRegion) UpdateVM(instanceId, name string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *SRegion) RebuildRoot(ctx context.Context, instanceId, passwd, publicKeyName string) error {
|
||||
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067876349.html
|
||||
// 返回job id
|
||||
func (self *SRegion) RebuildRoot(ctx context.Context, instanceId, passwd, publicKeyName string) (string, error) {
|
||||
params := jsonutils.NewDict()
|
||||
reinstallObj := jsonutils.NewDict()
|
||||
// meta := jsonutils.NewDict()
|
||||
@@ -872,15 +944,21 @@ func (self *SRegion) RebuildRoot(ctx context.Context, instanceId, passwd, public
|
||||
} else if len(publicKeyName) > 0 {
|
||||
reinstallObj.Add(jsonutils.NewString(publicKeyName), "keyname")
|
||||
} else {
|
||||
return fmt.Errorf("both password and publicKey are empty.")
|
||||
return "", fmt.Errorf("both password and publicKey are empty.")
|
||||
}
|
||||
|
||||
params.Add(reinstallObj, "os-reinstall")
|
||||
_, err := self.ecsClient.Servers.PerformAction2("reinstallos", instanceId, params, "")
|
||||
return err
|
||||
ret, err := self.ecsClient.Servers.PerformAction2("reinstallos", instanceId, params, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return ret.GetString("job_id")
|
||||
}
|
||||
|
||||
func (self *SRegion) ChangeRoot(ctx context.Context, instanceId, imageId, passwd, publicKeyName string) error {
|
||||
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067876971.html
|
||||
// 返回job id
|
||||
func (self *SRegion) ChangeRoot(ctx context.Context, instanceId, imageId, passwd, publicKeyName string) (string, error) {
|
||||
params := jsonutils.NewDict()
|
||||
changeOsObj := jsonutils.NewDict()
|
||||
// meta := jsonutils.NewDict()
|
||||
@@ -890,14 +968,18 @@ func (self *SRegion) ChangeRoot(ctx context.Context, instanceId, imageId, passwd
|
||||
} else if len(publicKeyName) > 0 {
|
||||
changeOsObj.Add(jsonutils.NewString(publicKeyName), "keyname")
|
||||
} else {
|
||||
return fmt.Errorf("both password and publicKey are empty.")
|
||||
return "", fmt.Errorf("both password and publicKey are empty.")
|
||||
}
|
||||
|
||||
changeOsObj.Add(jsonutils.NewString(imageId), "imageid")
|
||||
params.Add(changeOsObj, "os-change")
|
||||
|
||||
_, err := self.ecsClient.Servers.PerformAction2("changeos", instanceId, params, "")
|
||||
return err
|
||||
ret, err := self.ecsClient.Servers.PerformAction2("changeos", instanceId, params, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return ret.GetString("job_id")
|
||||
}
|
||||
|
||||
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212692.html
|
||||
|
||||
+111
-12
@@ -1,6 +1,19 @@
|
||||
package huawei
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudprovider"
|
||||
)
|
||||
|
||||
type SOrder struct {
|
||||
ErrorCode *string `json:"error_code"` // 只有失败时才返回此参数
|
||||
ErrorMsg *string `json:"error_msg"` //只有失败时才返回此参数
|
||||
TotalSize int `json:"totalSize"` // 只有成功时才返回此参数
|
||||
Resources []SResource `json:"resources"`
|
||||
}
|
||||
|
||||
type SResource struct {
|
||||
ResourceID string `json:"resourceId"`
|
||||
@@ -11,37 +24,123 @@ type SResource struct {
|
||||
Status int64 `json:"status"`
|
||||
}
|
||||
|
||||
/*
|
||||
获取订单信息 https://support.huaweicloud.com/api-oce/api_order_00001.html
|
||||
*/
|
||||
func (self *SRegion) GetAllResByOrderId(orderId string) ([]SResource, error) {
|
||||
type SResourceDetail struct {
|
||||
ID string `json:"id"`
|
||||
Status int64 `json:"status"`
|
||||
ResourceID string `json:"resource_id"`
|
||||
ResourceName string `json:"resource_name"`
|
||||
RegionCode string `json:"region_code"`
|
||||
CloudServiceTypeCode string `json:"cloud_service_type_code"`
|
||||
ResourceTypeCode string `json:"resource_type_code"`
|
||||
ResourceSpecCode string `json:"resource_spec_code"`
|
||||
ProjectCode string `json:"project_code"`
|
||||
ProductID string `json:"product_id"`
|
||||
MainResourceID string `json:"main_resource_id"`
|
||||
IsMainResource int64 `json:"is_main_resource"`
|
||||
ValidTime time.Time `json:"valid_time"`
|
||||
ExpireTime time.Time `json:"expire_time"`
|
||||
NextOperationPolicy string `json:"next_operation_policy"`
|
||||
}
|
||||
|
||||
func (self *SRegion) getDomianId() (string, error) {
|
||||
domains, err := self.client.getEnabledDomains()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
if domains == nil || len(domains) == 0 {
|
||||
return nil, fmt.Errorf("GetAllResByOrderId domain is empty")
|
||||
return "", fmt.Errorf("GetAllResByOrderId domain is empty")
|
||||
} else if len(domains) > 1 {
|
||||
// not supported??
|
||||
return nil, fmt.Errorf("GetAllResByOrderId mutliple domain(%d) found", len(domains))
|
||||
return "", fmt.Errorf("GetAllResByOrderId mutliple domain(%d) found", len(domains))
|
||||
}
|
||||
|
||||
err = self.ecsClient.Orders.SetDomainId(domains[0].ID)
|
||||
return domains[0].ID, nil
|
||||
}
|
||||
|
||||
/*
|
||||
获取订单信息 https://support.huaweicloud.com/api-oce/api_order_00001.html
|
||||
*/
|
||||
func (self *SRegion) GetOrder(orderId string) (SOrder, error) {
|
||||
var order SOrder
|
||||
domain, err := self.getDomianId()
|
||||
if err != nil {
|
||||
return order, err
|
||||
}
|
||||
|
||||
err = self.ecsClient.Orders.SetDomainId(domain)
|
||||
if err != nil {
|
||||
return order, err
|
||||
}
|
||||
|
||||
err = DoGet(self.ecsClient.Orders.Get, orderId, nil, &order)
|
||||
return order, err
|
||||
}
|
||||
|
||||
/*
|
||||
获取订单资源详情列表 https://support.huaweicloud.com/api-oce/zh-cn_topic_0084961226.html
|
||||
*/
|
||||
func (self *SRegion) GetOrderResources(orderId string, resource_ids []string, only_main_resource bool) ([]SResourceDetail, error) {
|
||||
domain, err := self.getDomianId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := self.ecsClient.Orders.Get(orderId, nil)
|
||||
err = self.ecsClient.Orders.SetDomainId(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resources := make([]SResource, 0)
|
||||
err = resp.Unmarshal(&resources, "resources")
|
||||
resources := make([]SResourceDetail, 0)
|
||||
queries := map[string]string{"customer_id": domain}
|
||||
if len(orderId) > 0 {
|
||||
queries["order_id"] = orderId
|
||||
}
|
||||
|
||||
if len(resource_ids) > 0 {
|
||||
queries["resource_ids"] = strings.Join(resource_ids, ",")
|
||||
}
|
||||
|
||||
if only_main_resource {
|
||||
queries["only_main_resource"] = "1"
|
||||
}
|
||||
|
||||
err = doListAll(self.ecsClient.Orders.GetPeriodResourceList, queries, &resources)
|
||||
return resources, err
|
||||
}
|
||||
|
||||
/*
|
||||
获取资源详情 https://support.huaweicloud.com/api-oce/zh-cn_topic_0084961226.html
|
||||
*/
|
||||
func (self *SRegion) GetOrderResourceDetail(resourceId string) (SResourceDetail, error) {
|
||||
var res SResourceDetail
|
||||
if len(resourceId) == 0 {
|
||||
return res, fmt.Errorf("GetOrderResourceDetail resource id should not be empty")
|
||||
}
|
||||
|
||||
resources, err := self.GetOrderResources("", []string{resourceId}, false)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
switch len(resources) {
|
||||
case 0:
|
||||
return res, cloudprovider.ErrNotFound
|
||||
case 1:
|
||||
return resources[0], nil
|
||||
default:
|
||||
return res, fmt.Errorf("%d resources with id %s found, Expect 1", len(resources), resourceId)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SRegion) GetAllResByOrderId(orderId string) ([]SResource, error) {
|
||||
order, err := self.GetOrder(orderId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return order.Resources, nil
|
||||
}
|
||||
|
||||
func (self *SRegion) getAllResByType(orderId string, resourceType string) ([]SResource, error) {
|
||||
res, err := self.GetAllResByOrderId(orderId)
|
||||
if err != nil {
|
||||
|
||||
@@ -338,6 +338,8 @@ func (self *SRegion) SyncSecurityGroup(secgroupId string, vpcId string, name str
|
||||
secgroupId = extID
|
||||
}
|
||||
|
||||
// 华为云默认deny。不需要显式指定
|
||||
rules = SecurityRuleSetToAllowSet(rules)
|
||||
return secgroupId, self.syncSecgroupRules(secgroupId, rules)
|
||||
}
|
||||
|
||||
@@ -608,6 +610,7 @@ func (self *SRegion) addSecurityGroupRules(secGrpId string, rule *secrules.Secur
|
||||
return nil
|
||||
}
|
||||
|
||||
// todo: icmp协议目前存在差异,华为云能指定icmp code,onecloud不支持
|
||||
func (self *SRegion) addSecurityGroupRule(secGrpId, direction, portStart, portEnd, protocol, ipNet string) error {
|
||||
params := jsonutils.NewDict()
|
||||
secgroupObj := jsonutils.NewDict()
|
||||
@@ -615,10 +618,11 @@ func (self *SRegion) addSecurityGroupRule(secGrpId, direction, portStart, portEn
|
||||
secgroupObj.Add(jsonutils.NewString(direction), "direction")
|
||||
secgroupObj.Add(jsonutils.NewString(ipNet), "remote_ip_prefix")
|
||||
secgroupObj.Add(jsonutils.NewString("IPV4"), "ethertype")
|
||||
if len(portStart) > 0 && portStart != "0" {
|
||||
// 端口为空或者1-65535
|
||||
if len(portStart) > 0 && portStart != "0" && portStart != "-1" {
|
||||
secgroupObj.Add(jsonutils.NewString(portStart), "port_range_min")
|
||||
}
|
||||
if len(portEnd) > 0 && portEnd != "0" {
|
||||
if len(portEnd) > 0 && portEnd != "0" && portEnd != "-1" {
|
||||
secgroupObj.Add(jsonutils.NewString(portEnd), "port_range_max")
|
||||
}
|
||||
if len(protocol) > 0 {
|
||||
|
||||
@@ -13,6 +13,8 @@ https://support.huaweicloud.com/usermanual-vpc/zh-cn_topic_0073379079.html
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sort"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/util/secrules"
|
||||
)
|
||||
@@ -68,6 +70,33 @@ func compatibleSecurityGroupRule(r SecurityGroupRule) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// 将安全组规则全部转换为等价的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()
|
||||
outRuleSet = outRuleSet.AllowList()
|
||||
|
||||
ret := secrules.SecurityRuleSet{}
|
||||
ret = append(ret, inRuleSet...)
|
||||
ret = append(ret, outRuleSet...)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (self *SSecurityGroup) GetId() string {
|
||||
return self.ID
|
||||
}
|
||||
@@ -192,7 +221,7 @@ func (self *SSecurityGroup) GetSecurityRule(ruleId string, withRuleId bool) (sec
|
||||
} else {
|
||||
desc = remoteRule.Description
|
||||
}
|
||||
// todo: icmp 可能不兼容
|
||||
// todo: icmp 可能不兼容。华为云能指定icmp code,但是onecloud端不支持
|
||||
rule := secrules.SecurityRule{
|
||||
Priority: 1,
|
||||
Action: secrules.SecurityRuleAllow,
|
||||
|
||||
+11
-1
@@ -1,7 +1,10 @@
|
||||
package huawei
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/log"
|
||||
)
|
||||
|
||||
func (self *SRegion) waitTaskStatus(serviceType string, taskId string, targetStatus string, interval time.Duration, timeout time.Duration) error {
|
||||
@@ -13,6 +16,8 @@ func (self *SRegion) waitTaskStatus(serviceType string, taskId string, targetSta
|
||||
}
|
||||
if status == targetStatus {
|
||||
break
|
||||
} else if status == TASK_FAIL {
|
||||
return fmt.Errorf("task %s failed", taskId)
|
||||
} else {
|
||||
time.Sleep(interval)
|
||||
}
|
||||
@@ -27,7 +32,12 @@ func (self *SRegion) GetTaskStatus(serviceType string, taskId string) (string, e
|
||||
return "", err
|
||||
}
|
||||
|
||||
return task.GetString("status")
|
||||
status, err := task.GetString("status")
|
||||
if status == TASK_FAIL {
|
||||
log.Debugf("task %s failed: %s", taskId, task.String())
|
||||
}
|
||||
|
||||
return status, err
|
||||
}
|
||||
|
||||
// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0022225398.html
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/util/httputils"
|
||||
"yunion.io/x/onecloud/pkg/util/huawei/client/manager"
|
||||
"yunion.io/x/onecloud/pkg/util/huawei/client/responses"
|
||||
"yunion.io/x/pkg/utils"
|
||||
)
|
||||
|
||||
// 常用的方法
|
||||
@@ -28,7 +29,7 @@ func unmarshalResult(resp jsonutils.JSONObject, respErr error, result interface{
|
||||
if respErr != nil {
|
||||
switch e := respErr.(type) {
|
||||
case *httputils.JSONClientError:
|
||||
if e.Code == 404 {
|
||||
if e.Code == 404 || utils.IsInStringArray(e.Class, NOT_FOUND_CODES) {
|
||||
return cloudprovider.ErrNotFound
|
||||
}
|
||||
return e
|
||||
|
||||
+6
@@ -27,6 +27,7 @@ var FULLISO_TIME_REG *regexp.Regexp
|
||||
var COMPACT_TIME_REG *regexp.Regexp
|
||||
var MYSQL_TIME_REG *regexp.Regexp
|
||||
var NORMAL_TIME_REG *regexp.Regexp
|
||||
var FULLNORMAL_TIME_REG *regexp.Regexp
|
||||
var RFC2882_TIME_REG *regexp.Regexp
|
||||
var EMAIL_REG *regexp.Regexp
|
||||
var CHINA_MOBILE_REG *regexp.Regexp
|
||||
@@ -56,6 +57,7 @@ func init() {
|
||||
COMPACT_TIME_REG = regexp.MustCompile(`^\d{14}$`)
|
||||
MYSQL_TIME_REG = regexp.MustCompile(`^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$`)
|
||||
NORMAL_TIME_REG = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$`)
|
||||
FULLNORMAL_TIME_REG = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}$`)
|
||||
RFC2882_TIME_REG = regexp.MustCompile(`[A-Z][a-z]{2}, [0-9]{1,2} [A-Z][a-z]{2} [0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2} [A-Z]{3}`)
|
||||
EMAIL_REG = regexp.MustCompile(`^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$`)
|
||||
CHINA_MOBILE_REG = regexp.MustCompile(`^1[0-9-]{10}$`)
|
||||
@@ -171,6 +173,10 @@ func MatchNormalTime(str string) bool {
|
||||
return NORMAL_TIME_REG.MatchString(str)
|
||||
}
|
||||
|
||||
func MatchFullNormalTime(str string) bool {
|
||||
return FULLNORMAL_TIME_REG.MatchString(str)
|
||||
}
|
||||
|
||||
func MatchRFC2882Time(str string) bool {
|
||||
return RFC2882_TIME_REG.MatchString(str)
|
||||
}
|
||||
|
||||
+7
@@ -26,6 +26,7 @@ const (
|
||||
FullIsoTimeFormat = "2006-01-02T15:04:05.000000Z"
|
||||
MysqlTimeFormat = "2006-01-02 15:04:05"
|
||||
NormalTimeFormat = "2006-01-02T15:04:05"
|
||||
FullNormalTimeFormat = "2006-01-02T15:04:05.000000"
|
||||
CompactTimeFormat = "20060102150405"
|
||||
DateFormat = "2006-01-02"
|
||||
ShortDateFormat = "20060102"
|
||||
@@ -84,6 +85,10 @@ func ParseNormalTime(str string) (time.Time, error) {
|
||||
return time.Parse(NormalTimeFormat, str)
|
||||
}
|
||||
|
||||
func ParseFullNormalTime(str string) (time.Time, error) {
|
||||
return time.Parse(FullNormalTimeFormat, str)
|
||||
}
|
||||
|
||||
func ParseCompactTime(str string) (time.Time, error) {
|
||||
return time.Parse(CompactTimeFormat, str)
|
||||
}
|
||||
@@ -111,6 +116,8 @@ func ParseTimeStr(str string) (time.Time, error) {
|
||||
return ParseMysqlTime(str)
|
||||
} else if regutils.MatchNormalTime(str) {
|
||||
return ParseNormalTime(str)
|
||||
} else if regutils.MatchFullNormalTime(str) {
|
||||
return ParseFullNormalTime(str)
|
||||
} else if regutils.MatchRFC2882Time(str) {
|
||||
return ParseRFC2882Time(str)
|
||||
} else if regutils.MatchCompactTime(str) {
|
||||
|
||||
Reference in New Issue
Block a user