From 7a447d31dbf91164507cc023dc02cc3a14e7d2ff Mon Sep 17 00:00:00 2001 From: TangBin Date: Wed, 13 Feb 2019 12:14:16 +0800 Subject: [PATCH 1/8] server delete etc fix --- pkg/util/huawei/consts.go | 32 ++++++++++++++++++++++++++++++++ pkg/util/huawei/region.go | 6 ++++-- pkg/util/huawei/securitygroup.go | 2 +- pkg/util/huawei/utils.go | 12 +++++++++++- 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/pkg/util/huawei/consts.go b/pkg/util/huawei/consts.go index ae0cc1ca0e..3b5b1a14c7 100644 --- a/pkg/util/huawei/consts.go +++ b/pkg/util/huawei/consts.go @@ -20,3 +20,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, +} diff --git a/pkg/util/huawei/region.go b/pkg/util/huawei/region.go index a09ee15cfd..1d1e9294b5 100644 --- a/pkg/util/huawei/region.go +++ b/pkg/util/huawei/region.go @@ -640,6 +640,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() @@ -647,10 +648,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 { diff --git a/pkg/util/huawei/securitygroup.go b/pkg/util/huawei/securitygroup.go index ead715c452..007d769072 100644 --- a/pkg/util/huawei/securitygroup.go +++ b/pkg/util/huawei/securitygroup.go @@ -194,7 +194,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, diff --git a/pkg/util/huawei/utils.go b/pkg/util/huawei/utils.go index 63a6e80036..c57febde2e 100644 --- a/pkg/util/huawei/utils.go +++ b/pkg/util/huawei/utils.go @@ -22,11 +22,21 @@ type deleteFunc2 func(ctx manager.IManagerContext, id string, spec string, param type listInCtxFunc func(ctx manager.IManagerContext, querys map[string]string) (*responses.ListResult, error) type listInCtxWithSpecFunc func(ctx manager.IManagerContext, spec string, querys map[string]string, responseKey string) (*responses.ListResult, error) +func notFound(code string) bool { + for _, c := range NOT_FOUND_CODES { + if code == c { + return true + } + } + + return false +} + func unmarshalResult(resp jsonutils.JSONObject, respErr error, result interface{}) error { if respErr != nil { switch e := respErr.(type) { case *httputils.JSONClientError: - if e.Code == 404 { + if e.Code == 404 || notFound(e.Class) { return cloudprovider.ErrNotFound } return e From 632d1118db50485040178a94be106bd6bed89178 Mon Sep 17 00:00:00 2001 From: TangBin Date: Wed, 13 Feb 2019 17:04:06 +0800 Subject: [PATCH 2/8] server delete etc fix --- pkg/compute/guestdrivers/managedvirtual.go | 6 ++++++ pkg/util/huawei/instance.go | 19 +++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/pkg/compute/guestdrivers/managedvirtual.go b/pkg/compute/guestdrivers/managedvirtual.go index 6e45485452..d096e21f4e 100644 --- a/pkg/compute/guestdrivers/managedvirtual.go +++ b/pkg/compute/guestdrivers/managedvirtual.go @@ -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 { diff --git a/pkg/util/huawei/instance.go b/pkg/util/huawei/instance.go index 8016abfb2b..c77c40acde 100644 --- a/pkg/util/huawei/instance.go +++ b/pkg/util/huawei/instance.go @@ -189,6 +189,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) } @@ -418,6 +424,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 +437,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 { @@ -828,10 +843,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 From 5fbc28f8aa10227bb3771900a6d008d9ca673d38 Mon Sep 17 00:00:00 2001 From: TangBin Date: Wed, 13 Feb 2019 18:07:18 +0800 Subject: [PATCH 3/8] server get disks fix --- pkg/util/huawei/instance.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/util/huawei/instance.go b/pkg/util/huawei/instance.go index c77c40acde..60909e7eac 100644 --- a/pkg/util/huawei/instance.go +++ b/pkg/util/huawei/instance.go @@ -281,6 +281,15 @@ func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { } disks[i].storage = storage idisks[i] = &disks[i] + // todo: 通过这个字段判断可能更准确 "OS-EXT-SRV-ATTR:root_device_name": "/dev/vda" + // 将系统盘放到第0个位置 + if disks[i].GetDiskType() == models.DISK_TYPE_SYS { + _temp := idisks[0] + idisks[0] = &disks[i] + idisks[i] = _temp + } else { + idisks[i] = &disks[i] + } } return idisks, nil } From d927f2dfc432c66a12c74e41ea00deccd7a5413a Mon Sep 17 00:00:00 2001 From: TangBin Date: Thu, 14 Feb 2019 12:28:36 +0800 Subject: [PATCH 4/8] rebuild root bugfix --- pkg/compute/guestdrivers/huawei.go | 4 +- pkg/util/huawei/consts.go | 5 +- pkg/util/huawei/disk.go | 9 +++- pkg/util/huawei/instance.go | 85 ++++++++++++++++++++++++------ pkg/util/huawei/task.go | 12 ++++- 5 files changed, 93 insertions(+), 22 deletions(-) diff --git a/pkg/compute/guestdrivers/huawei.go b/pkg/compute/guestdrivers/huawei.go index 53e0d01de0..45cd8531b3 100644 --- a/pkg/compute/guestdrivers/huawei.go +++ b/pkg/compute/guestdrivers/huawei.go @@ -184,11 +184,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 diff --git a/pkg/util/huawei/consts.go b/pkg/util/huawei/consts.go index 3b5b1a14c7..683497fb9f 100644 --- a/pkg/util/huawei/consts.go +++ b/pkg/util/huawei/consts.go @@ -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 ( diff --git a/pkg/util/huawei/disk.go b/pkg/util/huawei/disk.go index 78257d8649..3d72ea70e6 100644 --- a/pkg/util/huawei/disk.go +++ b/pkg/util/huawei/disk.go @@ -95,6 +95,7 @@ type SDisk struct { ConsistencygroupID string `json:"consistencygroup_id"` UpdatedAt string `json:"updated_at"` + DiskType string // 额外添加的字段用于标记是否为系统盘 /*下面这些字段也许不需要*/ ExpiredTime time.Time } @@ -233,8 +234,14 @@ func (self *SDisk) GetTemplateId() string { return self.VolumeImageMetadata.ImageID } +// Bootable 表示硬盘是否为启动盘。 +// 启动盘 != 系统盘(必须是启动盘且挂载在root device上) func (self *SDisk) GetDiskType() string { - if self.Bootable == "true" { + if self.Bootable != "true" || len(self.Attachments) == 0 { + return models.DISK_TYPE_DATA + } + + if self.DiskType == models.DISK_TYPE_SYS { return models.DISK_TYPE_SYS } else { return models.DISK_TYPE_DATA diff --git a/pkg/util/huawei/instance.go b/pkg/util/huawei/instance.go index 60909e7eac..104d9405ee 100644 --- a/pkg/util/huawei/instance.go +++ b/pkg/util/huawei/instance.go @@ -96,7 +96,6 @@ type SInstance struct { 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"` @@ -156,6 +155,25 @@ func compareSet(currentSet []string, newSet []string) (add []string, remove []st return add, remove, keep } +func markDiskType(server *SInstance, disk *SDisk) { + if disk.Bootable != "true" { + return + } + + if len(disk.Attachments) == 0 { + return + } + + for _, attachment := range disk.Attachments { + if attachment.ServerID == server.GetId() && attachment.Device == server.OSEXTSRVATTRRootDeviceName { + disk.DiskType = models.DISK_TYPE_SYS + return + } + } + + return +} + func (self *SInstance) GetId() string { return self.ID } @@ -216,9 +234,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) } @@ -281,6 +299,7 @@ func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { } disks[i].storage = storage idisks[i] = &disks[i] + markDiskType(self, &disks[i]) // todo: 通过这个字段判断可能更准确 "OS-EXT-SRV-ATTR:root_device_name": "/dev/vda" // 将系统盘放到第0个位置 if disks[i].GetDiskType() == models.DISK_TYPE_SYS { @@ -481,20 +500,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 { @@ -883,7 +922,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() @@ -893,15 +934,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() @@ -911,14 +958,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 diff --git a/pkg/util/huawei/task.go b/pkg/util/huawei/task.go index 0d3128c7b9..d62904094f 100644 --- a/pkg/util/huawei/task.go +++ b/pkg/util/huawei/task.go @@ -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 From b12f0252ebe631d9a642d0770a5c4d07f2d05e79 Mon Sep 17 00:00:00 2001 From: TangBin Date: Thu, 14 Feb 2019 12:31:43 +0800 Subject: [PATCH 5/8] remove useless todo --- pkg/util/huawei/instance.go | 1 - pkg/util/huawei/securitygroup.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/util/huawei/instance.go b/pkg/util/huawei/instance.go index 104d9405ee..95fe32c799 100644 --- a/pkg/util/huawei/instance.go +++ b/pkg/util/huawei/instance.go @@ -300,7 +300,6 @@ func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { disks[i].storage = storage idisks[i] = &disks[i] markDiskType(self, &disks[i]) - // todo: 通过这个字段判断可能更准确 "OS-EXT-SRV-ATTR:root_device_name": "/dev/vda" // 将系统盘放到第0个位置 if disks[i].GetDiskType() == models.DISK_TYPE_SYS { _temp := idisks[0] diff --git a/pkg/util/huawei/securitygroup.go b/pkg/util/huawei/securitygroup.go index 007d769072..78d8d99ad2 100644 --- a/pkg/util/huawei/securitygroup.go +++ b/pkg/util/huawei/securitygroup.go @@ -194,7 +194,7 @@ func (self *SSecurityGroup) GetSecurityRule(ruleId string, withRuleId bool) (sec } else { desc = remoteRule.Description } - // todo: icmp 可能不兼容。华为云能指定icmp code,onecloud不支持 + // todo: icmp 可能不兼容。华为云能指定icmp code,但是onecloud端不支持 rule := secrules.SecurityRule{ Priority: 1, Action: secrules.SecurityRuleAllow, From 37c79c9a23cbdd4c83343cddb7a929064f33ed81 Mon Sep 17 00:00:00 2001 From: TangBin Date: Thu, 14 Feb 2019 14:43:59 +0800 Subject: [PATCH 6/8] =?UTF-8?q?=E6=9B=BF=E6=8D=A2=E4=B8=8D=E5=BF=85?= =?UTF-8?q?=E8=A6=81=E7=9A=84=E9=87=8D=E5=A4=8Dfunc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/util/huawei/utils.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/pkg/util/huawei/utils.go b/pkg/util/huawei/utils.go index c57febde2e..efd06a62d4 100644 --- a/pkg/util/huawei/utils.go +++ b/pkg/util/huawei/utils.go @@ -9,6 +9,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" ) // 常用的方法 @@ -22,21 +23,11 @@ type deleteFunc2 func(ctx manager.IManagerContext, id string, spec string, param type listInCtxFunc func(ctx manager.IManagerContext, querys map[string]string) (*responses.ListResult, error) type listInCtxWithSpecFunc func(ctx manager.IManagerContext, spec string, querys map[string]string, responseKey string) (*responses.ListResult, error) -func notFound(code string) bool { - for _, c := range NOT_FOUND_CODES { - if code == c { - return true - } - } - - return false -} - func unmarshalResult(resp jsonutils.JSONObject, respErr error, result interface{}) error { if respErr != nil { switch e := respErr.(type) { case *httputils.JSONClientError: - if e.Code == 404 || notFound(e.Class) { + if e.Code == 404 || utils.IsInStringArray(e.Class, NOT_FOUND_CODES) { return cloudprovider.ErrNotFound } return e From a0f3423519752f2fe2b1f27d3d0c8bdafd48656c Mon Sep 17 00:00:00 2001 From: TangBin Date: Sat, 16 Feb 2019 15:59:47 +0800 Subject: [PATCH 7/8] bugfix --- pkg/util/huawei/client/modules/mod_orders.go | 14 ++- pkg/util/huawei/disk.go | 7 +- pkg/util/huawei/instance.go | 54 ++++---- pkg/util/huawei/order.go | 123 +++++++++++++++++-- 4 files changed, 154 insertions(+), 44 deletions(-) diff --git a/pkg/util/huawei/client/modules/mod_orders.go b/pkg/util/huawei/client/modules/mod_orders.go index 6df268518f..4faa8c4928 100644 --- a/pkg/util/huawei/client/modules/mod_orders.go +++ b/pkg/util/huawei/client/modules/mod_orders.go @@ -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") +} diff --git a/pkg/util/huawei/disk.go b/pkg/util/huawei/disk.go index 3d72ea70e6..28ace16af8 100644 --- a/pkg/util/huawei/disk.go +++ b/pkg/util/huawei/disk.go @@ -95,7 +95,6 @@ type SDisk struct { ConsistencygroupID string `json:"consistencygroup_id"` UpdatedAt string `json:"updated_at"` - DiskType string // 额外添加的字段用于标记是否为系统盘 /*下面这些字段也许不需要*/ ExpiredTime time.Time } @@ -237,11 +236,7 @@ func (self *SDisk) GetTemplateId() string { // Bootable 表示硬盘是否为启动盘。 // 启动盘 != 系统盘(必须是启动盘且挂载在root device上) func (self *SDisk) GetDiskType() string { - if self.Bootable != "true" || len(self.Attachments) == 0 { - return models.DISK_TYPE_DATA - } - - if self.DiskType == models.DISK_TYPE_SYS { + if self.Bootable == "true" { return models.DISK_TYPE_SYS } else { return models.DISK_TYPE_DATA diff --git a/pkg/util/huawei/instance.go b/pkg/util/huawei/instance.go index 95fe32c799..e4a0afddf9 100644 --- a/pkg/util/huawei/instance.go +++ b/pkg/util/huawei/instance.go @@ -91,7 +91,7 @@ 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"` @@ -114,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"` } @@ -155,23 +155,19 @@ func compareSet(currentSet []string, newSet []string) (add []string, remove []st return add, remove, keep } -func markDiskType(server *SInstance, disk *SDisk) { - if disk.Bootable != "true" { - return - } - - if len(disk.Attachments) == 0 { - return +// 启动盘 != 系统盘(必须是启动盘且挂载在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 { - disk.DiskType = models.DISK_TYPE_SYS - return + return true } } - return + return false } func (self *SInstance) GetId() string { @@ -265,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 { @@ -299,14 +304,11 @@ func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { } disks[i].storage = storage idisks[i] = &disks[i] - markDiskType(self, &disks[i]) // 将系统盘放到第0个位置 - if disks[i].GetDiskType() == models.DISK_TYPE_SYS { + if isBootDisk(self, &disks[i]) { _temp := idisks[0] idisks[0] = &disks[i] idisks[i] = _temp - } else { - idisks[i] = &disks[i] } } return idisks, nil @@ -733,6 +735,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()) diff --git a/pkg/util/huawei/order.go b/pkg/util/huawei/order.go index a6c906b194..af55deff3c 100644 --- a/pkg/util/huawei/order.go +++ b/pkg/util/huawei/order.go @@ -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 = DoList(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 { From fa49e138f26fbeb21a193ef48befb98dcbe37f99 Mon Sep 17 00:00:00 2001 From: TangBin Date: Sat, 16 Feb 2019 17:55:42 +0800 Subject: [PATCH 8/8] update vendor & bugfix --- Gopkg.lock | 5 +- pkg/util/huawei/disk.go | 57 ++++++++++++++++++- pkg/util/huawei/order.go | 2 +- pkg/util/huawei/region.go | 2 + pkg/util/huawei/securitygroup.go | 29 ++++++++++ .../yunion.io/x/pkg/util/regutils/regutils.go | 6 ++ .../x/pkg/util/timeutils/timeutils.go | 7 +++ 7 files changed, 104 insertions(+), 4 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index e8ae57e3c2..7e20fb308d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -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", diff --git a/pkg/util/huawei/disk.go b/pkg/util/huawei/disk.go index f3ffc34b73..58a131f9e1 100644 --- a/pkg/util/huawei/disk.go +++ b/pkg/util/huawei/disk.go @@ -270,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 "" } @@ -325,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 } // 华为云不支持重置 diff --git a/pkg/util/huawei/order.go b/pkg/util/huawei/order.go index af55deff3c..c408cfb865 100644 --- a/pkg/util/huawei/order.go +++ b/pkg/util/huawei/order.go @@ -105,7 +105,7 @@ func (self *SRegion) GetOrderResources(orderId string, resource_ids []string, on queries["only_main_resource"] = "1" } - err = DoList(self.ecsClient.Orders.GetPeriodResourceList, queries, &resources) + err = doListAll(self.ecsClient.Orders.GetPeriodResourceList, queries, &resources) return resources, err } diff --git a/pkg/util/huawei/region.go b/pkg/util/huawei/region.go index fbd8a01b30..4d686cd1f8 100644 --- a/pkg/util/huawei/region.go +++ b/pkg/util/huawei/region.go @@ -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) } diff --git a/pkg/util/huawei/securitygroup.go b/pkg/util/huawei/securitygroup.go index 1b30fac95c..96f852a263 100644 --- a/pkg/util/huawei/securitygroup.go +++ b/pkg/util/huawei/securitygroup.go @@ -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 } diff --git a/vendor/yunion.io/x/pkg/util/regutils/regutils.go b/vendor/yunion.io/x/pkg/util/regutils/regutils.go index b196f2772f..6fd20266fc 100644 --- a/vendor/yunion.io/x/pkg/util/regutils/regutils.go +++ b/vendor/yunion.io/x/pkg/util/regutils/regutils.go @@ -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) } diff --git a/vendor/yunion.io/x/pkg/util/timeutils/timeutils.go b/vendor/yunion.io/x/pkg/util/timeutils/timeutils.go index ed6522dd37..d92e34844f 100644 --- a/vendor/yunion.io/x/pkg/util/timeutils/timeutils.go +++ b/vendor/yunion.io/x/pkg/util/timeutils/timeutils.go @@ -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) {