From 370a7f94d8f94b731df673b55f7874fdfb2db7bf Mon Sep 17 00:00:00 2001 From: tb365 Date: Tue, 24 Aug 2021 18:08:22 +0800 Subject: [PATCH] hcso support sync vm host --- pkg/compute/guestdrivers/base.go | 4 ++ pkg/compute/guestdrivers/huaweistack.go | 41 +++++++++++ pkg/compute/guestdrivers/managedvirtual.go | 34 +++++---- pkg/compute/models/guestdrivers.go | 1 + pkg/compute/models/hosts.go | 14 ++-- pkg/compute/models/storagecachedimages.go | 3 + pkg/compute/models/storagecaches.go | 1 - pkg/multicloud/huaweistack/host.go | 83 ++++++++++++++++------ pkg/multicloud/huaweistack/instance.go | 4 ++ pkg/multicloud/huaweistack/region.go | 14 +++- pkg/multicloud/huaweistack/shell/hosts.go | 34 +++++++++ pkg/multicloud/huaweistack/zone.go | 76 +++++++++++++++++--- 12 files changed, 258 insertions(+), 51 deletions(-) create mode 100644 pkg/multicloud/huaweistack/shell/hosts.go diff --git a/pkg/compute/guestdrivers/base.go b/pkg/compute/guestdrivers/base.go index 5dae113a98..356aeeb33b 100644 --- a/pkg/compute/guestdrivers/base.go +++ b/pkg/compute/guestdrivers/base.go @@ -304,6 +304,10 @@ func (self *SBaseGuestDriver) RemoteDeployGuestForCreate(ctx context.Context, us return nil, cloudprovider.ErrNotSupported } +func (self *SBaseGuestDriver) RemoteDeployGuestSyncHost(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, host *models.SHost, iVM cloudprovider.ICloudVM) (cloudprovider.ICloudHost, error) { + return nil, cloudprovider.ErrNotSupported +} + func (self *SBaseGuestDriver) RemoteActionAfterGuestCreated(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, host *models.SHost, ivm cloudprovider.ICloudVM, desc *cloudprovider.SManagedVMCreateConfig) { return } diff --git a/pkg/compute/guestdrivers/huaweistack.go b/pkg/compute/guestdrivers/huaweistack.go index 9840b6473e..603fceda45 100644 --- a/pkg/compute/guestdrivers/huaweistack.go +++ b/pkg/compute/guestdrivers/huaweistack.go @@ -15,11 +15,17 @@ package guestdrivers import ( + "context" + "database/sql" "fmt" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" "yunion.io/x/pkg/utils" + "yunion.io/x/sqlchemy" api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" @@ -143,6 +149,41 @@ func (self *SHuaweiCloudStackGuestDriver) GetInstanceCapability() cloudprovider. } } +func (self *SHuaweiCloudStackGuestDriver) RemoteDeployGuestSyncHost(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, host *models.SHost, iVM cloudprovider.ICloudVM) (cloudprovider.ICloudHost, error) { + if hostId := iVM.GetIHostId(); len(hostId) > 0 { + nh, err := db.FetchByExternalIdAndManagerId(models.HostManager, hostId, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { + return q.Equals("manager_id", host.ManagerId) + }) + if err != nil { + log.Debugf("failed to found new hostId(%s) for ivm %s(%s) error: %v", hostId, guest.Name, guest.Id, err) + if errors.Cause(err) != sql.ErrNoRows { + return nil, errors.Wrap(err, "FetchByExternalIdAndManagerId") + } + + // HYPERVISOR_HUAWEI_CLOUD_STACK VM被部署到一台全新的宿主机 + zone, err := host.GetZone() + if err != nil { + log.Warningf("host %s GetZone: %s", host.GetId(), err) + } else { + _host, err := models.HostManager.NewFromCloudHost(ctx, userCred, iVM.GetIHost(), host.GetCloudprovider(), zone) + if err != nil { + log.Warningf("NewFromCloudHost %s: %s", iVM.GetIHostId(), err) + } else { + host = _host + } + } + } else { + host = nh.(*models.SHost) + } + } + + if host.GetId() != guest.HostId { + guest.OnScheduleToHost(ctx, userCred, host.GetId()) + } + + return host.GetIHost() +} + func (self *SHuaweiCloudStackGuestDriver) IsSupportedBillingCycle(bc billing.SBillingCycle) bool { months := bc.GetMonths() if (months >= 1 && months <= 9) || (months == 12) || (months == 24) || (months == 36) { diff --git a/pkg/compute/guestdrivers/managedvirtual.go b/pkg/compute/guestdrivers/managedvirtual.go index f4aa0027d8..ccbf3a2906 100644 --- a/pkg/compute/guestdrivers/managedvirtual.go +++ b/pkg/compute/guestdrivers/managedvirtual.go @@ -491,24 +491,16 @@ func (self *SManagedVirtualizedGuestDriver) RemoteDeployGuestForCreate(ctx conte } db.SetExternalId(guest, userCred, iVM.GetGlobalId()) - - if hostId := iVM.GetIHostId(); len(hostId) > 0 { - host, err := db.FetchByExternalIdAndManagerId(models.HostManager, hostId, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { - return q.Equals("manager_id", host.ManagerId) - }) - if err != nil { - log.Warningf("failed to found new hostId(%s) for ivm %s(%s) error: %v", hostId, guest.Name, guest.Id, err) - } else if host.GetId() != guest.HostId { - guest.OnScheduleToHost(ctx, userCred, host.GetId()) - } - } - return iVM, nil }() - if err != nil { return nil, err } + // iVM 实际所在的ihost 可能和 调度选择的host不是同一个,此处根据iVM实际所在host,重新同步 + ihost, err = guest.GetDriver().RemoteDeployGuestSyncHost(ctx, userCred, guest, host, iVM) + if err != nil { + return nil, errors.Wrap(err, "RemoteDeployGuestSyncHost") + } initialState := guest.GetDriver().GetGuestInitialStateAfterCreate() log.Debugf("VMcreated %s, wait status %s ...", iVM.GetGlobalId(), initialState) @@ -552,6 +544,22 @@ func (self *SManagedVirtualizedGuestDriver) RemoteDeployGuestForCreate(ctx conte return data, nil } +func (self *SManagedVirtualizedGuestDriver) RemoteDeployGuestSyncHost(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, host *models.SHost, iVM cloudprovider.ICloudVM) (cloudprovider.ICloudHost, error) { + if hostId := iVM.GetIHostId(); len(hostId) > 0 { + nh, err := db.FetchByExternalIdAndManagerId(models.HostManager, hostId, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { + return q.Equals("manager_id", host.ManagerId) + }) + if err != nil { + log.Warningf("failed to found new hostId(%s) for ivm %s(%s) error: %v", hostId, guest.Name, guest.Id, err) + } else if nh.GetId() != guest.HostId { + guest.OnScheduleToHost(ctx, userCred, nh.GetId()) + host = nh.(*models.SHost) + } + } + + return host.GetIHost() +} + func (self *SManagedVirtualizedGuestDriver) RemoteDeployGuestForDeploy(ctx context.Context, guest *models.SGuest, ihost cloudprovider.ICloudHost, task taskman.ITask, desc cloudprovider.SManagedVMCreateConfig) (jsonutils.JSONObject, error) { iVM, err := ihost.GetIVMById(guest.GetExternalId()) if err != nil || iVM == nil { diff --git a/pkg/compute/models/guestdrivers.go b/pkg/compute/models/guestdrivers.go index 7f58f7aa12..de42383aed 100644 --- a/pkg/compute/models/guestdrivers.go +++ b/pkg/compute/models/guestdrivers.go @@ -91,6 +91,7 @@ type IGuestDriver interface { RequestDeployGuestOnHost(ctx context.Context, guest *SGuest, host *SHost, task taskman.ITask) error RemoteDeployGuestForCreate(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, host *SHost, desc cloudprovider.SManagedVMCreateConfig) (jsonutils.JSONObject, error) + RemoteDeployGuestSyncHost(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, host *SHost, iVM cloudprovider.ICloudVM) (cloudprovider.ICloudHost, error) RemoteActionAfterGuestCreated(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, host *SHost, iVM cloudprovider.ICloudVM, desc *cloudprovider.SManagedVMCreateConfig) RemoteDeployGuestForDeploy(ctx context.Context, guest *SGuest, ihost cloudprovider.ICloudHost, task taskman.ITask, desc cloudprovider.SManagedVMCreateConfig) (jsonutils.JSONObject, error) RemoteDeployGuestForRebuildRoot(ctx context.Context, guest *SGuest, ihost cloudprovider.ICloudHost, task taskman.ITask, desc cloudprovider.SManagedVMCreateConfig) (jsonutils.JSONObject, error) diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index 370db77171..975e8be658 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -1716,7 +1716,7 @@ func (manager *SHostManager) SyncHosts(ctx context.Context, userCred mcclient.To } } for i := 0; i < len(added); i += 1 { - new, err := manager.newFromCloudHost(ctx, userCred, added[i], provider, zone) + new, err := manager.NewFromCloudHost(ctx, userCred, added[i], provider, zone) if err != nil { syncResult.AddError(err) } else { @@ -1971,7 +1971,7 @@ func (s *SHost) syncSchedtags(ctx context.Context, userCred mcclient.TokenCreden return nil } -func (manager *SHostManager) newFromCloudHost(ctx context.Context, userCred mcclient.TokenCredential, extHost cloudprovider.ICloudHost, provider *SCloudprovider, izone *SZone) (*SHost, error) { +func (manager *SHostManager) NewFromCloudHost(ctx context.Context, userCred mcclient.TokenCredential, extHost cloudprovider.ICloudHost, provider *SCloudprovider, izone *SZone) (*SHost, error) { host := SHost{} host.SetModelManager(manager, &host) @@ -2042,11 +2042,11 @@ func (manager *SHostManager) newFromCloudHost(ctx context.Context, userCred mccl lockman.LockRawObject(ctx, manager.Keyword(), "name") defer lockman.ReleaseRawObject(ctx, manager.Keyword(), "name") - newName, err := db.GenerateName(ctx, manager, userCred, extHost.GetName()) - if err != nil { - return errors.Wrapf(err, "db.GenerateName") - } - host.Name = newName + //newName, err := db.GenerateName(ctx, manager, userCred, extHost.GetName()) + //if err != nil { + // return errors.Wrapf(err, "db.GenerateName") + //} + host.Name = extHost.GetName() return manager.TableSpec().Insert(ctx, &host) }() diff --git a/pkg/compute/models/storagecachedimages.go b/pkg/compute/models/storagecachedimages.go index 2110be4623..5111ec839f 100644 --- a/pkg/compute/models/storagecachedimages.go +++ b/pkg/compute/models/storagecachedimages.go @@ -435,6 +435,9 @@ func (self *SStoragecachedimage) syncRemoveCloudImage(ctx context.Context, userC func (self *SStoragecachedimage) syncWithCloudImage(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, image cloudprovider.ICloudImage, managerId string) error { cachedImage := self.GetCachedimage() + if len(self.ExternalId) == 0 { + self.SetExternalId(cachedImage.GetExternalId()) + } if len(cachedImage.ExternalId) > 0 { self.SetStatus(userCred, image.GetStatus(), "") return cachedImage.syncWithCloudImage(ctx, userCred, ownerId, image, managerId) diff --git a/pkg/compute/models/storagecaches.go b/pkg/compute/models/storagecaches.go index 1169497219..2f8652ae0c 100644 --- a/pkg/compute/models/storagecaches.go +++ b/pkg/compute/models/storagecaches.go @@ -715,7 +715,6 @@ func (cache *SStoragecache) syncCloudImages( commondb := make([]SStoragecachedimage, 0) commonext := make([]cloudprovider.ICloudImage, 0) added := make([]cloudprovider.ICloudImage, 0) - err := compare.CompareSets(localCachedImages, remoteImages, &removed, &commondb, &commonext, &added) if err != nil { syncResult.Error(errors.Wrapf(err, "compare.CompareSets")) diff --git a/pkg/multicloud/huaweistack/host.go b/pkg/multicloud/huaweistack/host.go index b393cfcbc7..cebda49ffe 100644 --- a/pkg/multicloud/huaweistack/host.go +++ b/pkg/multicloud/huaweistack/host.go @@ -32,20 +32,31 @@ import ( type SHost struct { multicloud.SHostBase zone *SZone + vms []SInstance + // 华为私有云没有直接列出host的接口,所有账号下的host都是通过VM反向解析出来的 + // 当账号下没有虚拟机时,如果没有host,会导致调度找不到可用的HOST。 + // 因此,为了避免上述情况始终会在每个zone下返回一台虚拟的host + IsFake bool projectId string + Id string + Name string } func (self *SHost) GetId() string { - return fmt.Sprintf("%s-%s", self.zone.region.client.cpcfg.Id, self.zone.GetId()) + return self.Id } func (self *SHost) GetName() string { - return fmt.Sprintf("%s-%s", self.zone.region.client.cpcfg.Name, self.zone.GetId()) + if len(self.Name) > 0 { + return self.Name + } + + return self.Id } func (self *SHost) GetGlobalId() string { - return fmt.Sprintf("%s-%s", self.zone.region.client.cpcfg.Id, self.zone.GetId()) + return self.Id } func (self *SHost) GetStatus() string { @@ -53,36 +64,61 @@ func (self *SHost) GetStatus() string { } func (self *SHost) Refresh() error { - return nil + _, err := self.getVMs() + return errors.Wrap(err, "getVMs") } func (self *SHost) IsEmulated() bool { - return true + return self.IsFake } func (self *SHost) GetIVMs() ([]cloudprovider.ICloudVM, error) { - vms, err := self.zone.region.GetInstances() - if err != nil { - return nil, err - } - - filtedVms := make([]SInstance, 0) - for i := range vms { - if vms[i].OSEXTAZAvailabilityZone == self.zone.GetId() { - filtedVms = append(filtedVms, vms[i]) + var vms []SInstance + var err error + if self.vms != nil { + vms = self.vms + } else { + vms, err = self.getVMs() + if err != nil { + return nil, err } } - ivms := make([]cloudprovider.ICloudVM, len(filtedVms)) - for i := 0; i < len(filtedVms); i += 1 { - filtedVms[i].host = self - ivms[i] = &filtedVms[i] + ret := make([]cloudprovider.ICloudVM, len(vms)) + for i := range vms { + vm := vms[i] + vm.host = self + ret[i] = &vm } - return ivms, nil + + return ret, nil +} + +func (self *SHost) getVMs() ([]SInstance, error) { + vms, err := self.zone.region.GetInstances() + if err != nil { + return nil, errors.Wrap(err, "GetInstances") + } + + ret := []SInstance{} + for i := range vms { + vm := vms[i] + if vm.OSEXTAZAvailabilityZone == self.GetId() && vm.HostID == self.GetId() { + vm.host = self + ret = append(ret, vm) + } + } + + self.vms = ret + return ret, nil } func (self *SHost) GetIVMById(id string) (cloudprovider.ICloudVM, error) { vm, err := self.zone.region.GetInstanceByID(id) + if vm.HostID != self.GetId() { + return nil, errors.Wrap(cloudprovider.ErrNotFound, "GetInstanceByID") + } + vm.host = self return &vm, err } @@ -118,6 +154,8 @@ func (self *SHost) GetAccessMac() string { func (self *SHost) GetSysInfo() jsonutils.JSONObject { info := jsonutils.NewDict() info.Add(jsonutils.NewString(CLOUD_PROVIDER_HUAWEI), "manufacture") + info.Add(jsonutils.NewString(self.GetId()), "id") + info.Add(jsonutils.NewString(self.GetName()), "name") return info } @@ -171,6 +209,10 @@ func (self *SHost) GetInstanceById(instanceId string) (*SInstance, error) { return nil, err } + if instance.HostID != self.GetId() { + return nil, errors.Wrap(cloudprovider.ErrNotFound, "GetInstanceByID") + } + instance.host = self return &instance, nil } @@ -188,7 +230,8 @@ func (self *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudpr return nil, err } - vm, err := self.GetInstanceById(vmId) + // VM实际调度到的host, 可能不是当前host.因此需要改写host信息 + vm, err := self.zone.region.GetIVMById(vmId) if err != nil { return nil, err } diff --git a/pkg/multicloud/huaweistack/instance.go b/pkg/multicloud/huaweistack/instance.go index 4993457b89..2c57b2eea2 100644 --- a/pkg/multicloud/huaweistack/instance.go +++ b/pkg/multicloud/huaweistack/instance.go @@ -362,6 +362,10 @@ func (self *SInstance) GetIHost() cloudprovider.ICloudHost { return self.host } +func (self *SInstance) GetIHostId() string { + return self.host.GetGlobalId() +} + func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { err := self.Refresh() if err != nil { diff --git a/pkg/multicloud/huaweistack/region.go b/pkg/multicloud/huaweistack/region.go index b76a62dbea..b8c4947558 100644 --- a/pkg/multicloud/huaweistack/region.go +++ b/pkg/multicloud/huaweistack/region.go @@ -76,7 +76,7 @@ func (self *SRegion) getECSClient() (*client.Client, error) { regionId := strings.Split(project.Name, "_")[0] if regionId != self.ID { - // log.Debugf("project %s not in region %s", self.client.projectId, self.ID) + // log.Debugf("project %s not in region %s", self.client.ProjectId, self.ID) return nil, errors.Error("region and project mismatch") } } @@ -153,6 +153,18 @@ func (self *SRegion) GetIVMById(id string) (cloudprovider.ICloudVM, error) { if err != nil { return nil, err } + + zone, err := self.getZoneById(instance.OSEXTAZAvailabilityZone) + if err != nil { + return nil, errors.Wrap(err, "getZoneById") + } + instance.host = &SHost{ + zone: zone, + vms: nil, + projectId: self.client.projectId, + Id: instance.HostID, + Name: instance.OSEXTSRVATTRHost, + } return &instance, err } diff --git a/pkg/multicloud/huaweistack/shell/hosts.go b/pkg/multicloud/huaweistack/shell/hosts.go new file mode 100644 index 0000000000..6dad7c7f54 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/hosts.go @@ -0,0 +1,34 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type HostListOptions struct { + } + shellutils.R(&HostListOptions{}, "host-list", "List hosts", func(cli *huawei.SRegion, args *HostListOptions) error { + hosts, e := cli.GetIHosts() + if e != nil { + return e + } + + printList(hosts, 0, 0, 0, []string{}) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/zone.go b/pkg/multicloud/huaweistack/zone.go index a711c7946c..6d5a4457e2 100644 --- a/pkg/multicloud/huaweistack/zone.go +++ b/pkg/multicloud/huaweistack/zone.go @@ -40,7 +40,7 @@ type SZone struct { multicloud.SResourceBase multicloud.HuaweiTags region *SRegion - host *SHost + ihosts []cloudprovider.ICloudHost iwires []cloudprovider.ICloudWire istorages []cloudprovider.ICloudStorage @@ -81,11 +81,62 @@ func (self *SZone) fetchStorages() error { return nil } -func (self *SZone) getHost() *SHost { - if self.host == nil { - self.host = &SHost{zone: self, projectId: self.region.client.projectId} +// 华为私有云没有直接列出host的接口,所有账号下的host都是通过VM反向解析出来的 +// 当账号下没有虚拟机时,如果没有host,会导致调度找不到可用的HOST。 +// 因此,为了避免上述情况始终会在每个zone下返回一台虚拟的host +func (self *SZone) getEmulatedHost() SHost { + return SHost{ + zone: self, + vms: nil, + IsFake: true, + projectId: self.region.client.projectId, + Id: fmt.Sprintf("%s-%s", self.region.client.cpcfg.Id, self.GetId()), + Name: fmt.Sprintf("%s-%s", self.region.client.cpcfg.Name, self.GetId()), } - return self.host +} + +func (self *SZone) getHosts() ([]cloudprovider.ICloudHost, error) { + if self.ihosts != nil { + return self.ihosts, nil + } + + vms, err := self.region.GetInstances() + if err != nil { + return nil, errors.Wrap(err, "GetInstances") + } + + hosts := map[string]string{} + hostVms := map[string][]SInstance{} + for i := range vms { + vm := vms[i] + if vm.OSEXTAZAvailabilityZone == self.GetId() { + hosts[vm.HostID] = vm.OSEXTSRVATTRHost + if _, ok := hostVms[vm.HostID]; ok { + hostVms[vm.HostID] = append(hostVms[vm.HostID], vm) + } else { + hostVms[vm.HostID] = []SInstance{vm} + } + } + } + + fakeHost := self.getEmulatedHost() + ihosts := []cloudprovider.ICloudHost{&fakeHost} + for k, _ := range hosts { + h := SHost{ + zone: self, + projectId: self.region.client.projectId, + Id: k, + Name: hosts[k], + } + for i := range hostVms[k] { + hostVms[k][i].host = &h + } + + h.vms = hostVms[k] + ihosts = append(ihosts, &h) + } + + return ihosts, nil } func (self *SZone) GetId() string { @@ -124,14 +175,21 @@ func (self *SZone) GetIRegion() cloudprovider.ICloudRegion { } func (self *SZone) GetIHosts() ([]cloudprovider.ICloudHost, error) { - return []cloudprovider.ICloudHost{self.getHost()}, nil + return self.getHosts() } func (self *SZone) GetIHostById(id string) (cloudprovider.ICloudHost, error) { - host := self.getHost() - if host.GetGlobalId() == id { - return host, nil + ihosts, err := self.getHosts() + if err != nil { + return nil, errors.Wrap(err, "getHosts") } + + for i := range ihosts { + if ihosts[i].GetGlobalId() == id { + return ihosts[i], nil + } + } + return nil, cloudprovider.ErrNotFound }