Merge pull request #4336 from ioito/hotfix/qx-openstack-optimized

fix: 优化支持openstack重装系统
This commit is contained in:
yunion-ci-robot
2019-12-27 01:13:30 +08:00
committed by GitHub
10 changed files with 402 additions and 36 deletions
+1
View File
@@ -59,6 +59,7 @@ const (
// openstack
STORAGE_OPENSTACK_ISCSI = "iscsi"
STORAGE_OPENSTACK_NOVA = "nova"
// Ucloud storage type
STORAGE_UCLOUD_CLOUD_NORMAL = "CLOUD_NORMAL" // 普通云盘
+163 -2
View File
@@ -17,12 +17,18 @@ package guestdrivers
import (
"context"
"fmt"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/utils"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/cloudcommon/db/quotas"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/compute/models"
"yunion.io/x/onecloud/pkg/compute/options"
@@ -71,7 +77,7 @@ func (self *SOpenStackGuestDriver) IsSupportEip() bool {
}
func (self *SOpenStackGuestDriver) GetDefaultSysDiskBackend() string {
return api.STORAGE_OPENSTACK_ISCSI
return api.STORAGE_OPENSTACK_NOVA
}
func (self *SOpenStackGuestDriver) GetMinimalSysDiskSizeGb() int {
@@ -112,7 +118,7 @@ func (self *SOpenStackGuestDriver) IsNeedRestartForResetLoginInfo() bool {
}
func (self *SOpenStackGuestDriver) IsRebuildRootSupportChangeImage() bool {
return false
return true
}
func (self *SOpenStackGuestDriver) GetDeployStatus() ([]string, error) {
@@ -142,6 +148,13 @@ func (self *SOpenStackGuestDriver) ValidateCreateData(ctx context.Context, userC
if len(input.Eip) > 0 || input.EipBw > 0 {
return nil, httperrors.NewUnsupportOperationError("%s not support create virtual machine with eip", self.GetHypervisor())
}
for i := 1; i < len(input.Disks); i++ {
disk := input.Disks[i]
if disk.Backend == api.STORAGE_OPENSTACK_NOVA {
return nil, httperrors.NewUnsupportOperationError("data disk not support storage type %s", disk.Backend)
}
}
return input, nil
}
@@ -149,6 +162,154 @@ func (self *SOpenStackGuestDriver) GetGuestInitialStateAfterCreate() string {
return api.VM_RUNNING
}
func (self *SOpenStackGuestDriver) attachDisks(ctx context.Context, ihost cloudprovider.ICloudHost, instanceId string, diskIds []string) {
if len(diskIds) == 0 {
return
}
iVM, err := ihost.GetIVMById(instanceId)
if err != nil || iVM == nil {
log.Errorf("cannot find vm %s", instanceId)
return
}
for _, diskId := range diskIds {
err = iVM.AttachDisk(ctx, diskId)
if err != nil {
log.Errorf("failed to attach disk %s", diskId)
}
}
return
}
func (self *SOpenStackGuestDriver) RemoteDeployGuestForRebuildRoot(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 {
return nil, fmt.Errorf("cannot find vm %s(%s)", guest.Id, guest.Name)
}
instanceId := iVM.GetGlobalId()
diskId, err := func() (string, error) {
lockman.LockObject(ctx, guest)
defer lockman.ReleaseObject(ctx, guest)
sysDisk, err := guest.GetSystemDisk()
if err != nil {
return "", errors.Wrap(err, "guest.GetSystemDisk(")
}
storage := sysDisk.GetStorage()
if storage.StorageType == api.STORAGE_OPENSTACK_NOVA { //不通过镜像创建磁盘的机器
return iVM.RebuildRoot(ctx, desc.ExternalImageId, desc.Password, desc.PublicKey, desc.SysDisk.SizeGB)
}
iDisks, err := iVM.GetIDisks()
if err != nil {
return "", errors.Wrap(err, "iVM.GetIDisks")
}
detachDisks := []string{}
for i, iDisk := range iDisks {
if i != 0 {
err = iVM.DetachDisk(ctx, iDisk.GetGlobalId())
if err != nil {
return "", errors.Wrap(err, "iVM.DetachDisk")
}
detachDisks = append(detachDisks, iDisk.GetGlobalId())
}
}
defer self.attachDisks(ctx, ihost, instanceId, detachDisks)
eip, err := guest.GetEip()
if err == nil && eip != nil {
ieip, err := eip.GetIEip()
if err != nil {
return "", errors.Wrap(err, "eip.GetIEip")
}
err = ieip.Dissociate()
if err != nil {
return "", errors.Wrap(err, "ieip.Dissociate")
}
defer ieip.Associate(instanceId)
}
err = iVM.DeleteVM(ctx)
if err != nil {
return "", errors.Wrap(err, "iVM.DeleteVM")
}
err = cloudprovider.WaitDeleted(iVM, time.Second*5, time.Minute*10)
if err != nil {
return "", errors.Wrap(err, "WaitDeleted")
}
desc.DataDisks = []cloudprovider.SDiskInfo{}
iVM, err = ihost.CreateVM(&desc)
if err != nil {
return "", errors.Wrap(err, "ihost.CreateVM")
}
instanceId = iVM.GetGlobalId()
db.SetExternalId(guest, task.GetUserCred(), instanceId)
initialState := guest.GetDriver().GetGuestInitialStateAfterCreate()
log.Debugf("VMrebuildRoot %s new instance, wait status %s ...", iVM.GetGlobalId(), initialState)
cloudprovider.WaitStatus(iVM, initialState, time.Second*5, time.Second*1800)
iVM.StopVM(ctx, true)
iDisks, err = iVM.GetIDisks()
if err != nil {
return "", errors.Wrapf(err, "iVM.GetIDisks.AfterCreated")
}
for _, iDisk := range iDisks {
return iDisk.GetGlobalId(), nil
}
return "", fmt.Errorf("failed to found new instance system disk")
}()
if err != nil {
return nil, err
}
initialState := guest.GetDriver().GetGuestInitialStateAfterRebuild()
log.Debugf("VMrebuildRoot %s new diskID %s, wait status %s ...", iVM.GetGlobalId(), diskId, initialState)
err = cloudprovider.WaitStatus(iVM, initialState, time.Second*5, time.Second*1800)
if err != nil {
return nil, err
}
log.Debugf("VMrebuildRoot %s, and status is ready", iVM.GetGlobalId())
maxWaitSecs := 300
waited := 0
for {
// hack, wait disk number consistent
idisks, err := iVM.GetIDisks()
if err != nil {
log.Errorf("fail to find VM idisks %s", err)
return nil, err
}
if len(idisks) < len(desc.DataDisks)+1 {
if waited > maxWaitSecs {
log.Errorf("inconsistent disk number, wait timeout, must be something wrong on remote")
return nil, cloudprovider.ErrTimeout
}
log.Debugf("inconsistent disk number???? %d != %d", len(idisks), len(desc.DataDisks)+1)
time.Sleep(time.Second * 5)
waited += 5
} else {
if idisks[0].GetGlobalId() == diskId {
break
}
if waited > maxWaitSecs {
return nil, fmt.Errorf("inconsistent sys disk id after rebuild root")
}
log.Debugf("current system disk id inconsistent %s != %s, try after 5 seconds", idisks[0].GetGlobalId(), diskId)
time.Sleep(time.Second * 5)
waited += 5
}
}
data := fetchIVMinfo(desc, iVM, guest.Id, desc.Account, desc.Password, desc.PublicKey, "rebuild")
return data, nil
}
func (self *SOpenStackGuestDriver) GetGuestInitialStateAfterRebuild() string {
return api.VM_READY
}
+30 -2
View File
@@ -85,6 +85,7 @@ type VolumeImageMetadata struct {
}
type SDisk struct {
nova *SNovaStorage
storage *SStorage
multicloud.SDisk
@@ -168,6 +169,9 @@ func (disk *SDisk) GetId() string {
}
func (disk *SDisk) Delete(ctx context.Context) error {
if disk.nova != nil {
return nil
}
err := disk.storage.zone.region.DeleteDisk(disk.ID)
if err != nil {
return err
@@ -195,6 +199,9 @@ func (disk *SDisk) attachInstances(attachments []Attachment) error {
}
func (disk *SDisk) Resize(ctx context.Context, sizeMb int64) error {
if disk.nova != nil {
return cloudprovider.ErrNotSupported
}
instanceIds := []string{}
for _, attachement := range disk.Attachments {
@@ -227,6 +234,9 @@ func (disk *SDisk) IsEmulated() bool {
}
func (disk *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) {
if disk.nova != nil {
return disk.nova, nil
}
return disk.storage, nil
}
@@ -252,14 +262,20 @@ func (disk *SDisk) GetStatus() string {
}
func (disk *SDisk) Refresh() error {
new, err := disk.storage.zone.region.GetDisk(disk.ID)
if disk.nova != nil {
return nil
}
_disk, err := disk.storage.zone.region.GetDisk(disk.ID)
if err != nil {
return err
}
return jsonutils.Update(disk, new)
return jsonutils.Update(disk, _disk)
}
func (disk *SDisk) ResizeDisk(sizeMb int64) error {
if disk.nova != nil {
return cloudprovider.ErrNotSupported
}
return disk.storage.zone.region.ResizeDisk(disk.ID, sizeMb)
}
@@ -394,6 +410,9 @@ func (region *SRegion) ResetDisk(diskId, snapshotId string) error {
}
func (disk *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) {
if disk.nova != nil {
return nil, cloudprovider.ErrNotSupported
}
snapshot, err := disk.storage.zone.region.CreateSnapshot(disk.ID, name, desc)
if err != nil {
return nil, err
@@ -402,14 +421,23 @@ func (disk *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (clou
}
func (disk *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) {
if disk.nova != nil {
return nil, cloudprovider.ErrNotFound
}
return disk.storage.zone.region.GetISnapshotById(snapshotId)
}
func (disk *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) {
if disk.nova != nil {
return []cloudprovider.ICloudSnapshot{}, nil
}
return disk.storage.zone.region.GetSnapshots(disk.ID)
}
func (disk *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) {
if disk.nova != nil {
return "", cloudprovider.ErrNotSupported
}
return disk.ID, disk.storage.zone.region.ResetDisk(disk.ID, snapshotId)
}
+17 -2
View File
@@ -82,7 +82,22 @@ func (region *SRegion) GetFlavor(flavorId string) (*SFlavor, error) {
}
func (region *SRegion) SyncFlavor(name string, cpu, memoryMb, diskGB int) (string, error) {
return region.syncFlavor(name, cpu, memoryMb, diskGB)
id, err := region.syncFlavor(name, cpu, memoryMb, diskGB)
if err != nil {
return "", errors.Wrap(err, "syncFlavor")
}
flavor, err := region.GetFlavor(id)
if err != nil {
return "", errors.Wrapf(err, "region.GetFlavor(%s)", id)
}
if flavor.GetCpuCoreCount() != cpu || flavor.GetMemorySizeMB() != memoryMb || flavor.Disk != diskGB {
flavor, err = region.CreateFlavor(name, cpu, memoryMb, diskGB)
if err != nil {
return "", errors.Wrap(err, "CreateFlavor")
}
return flavor.ID, nil
}
return id, nil
}
func (region *SRegion) syncFlavor(name string, cpu, memoryMb, diskGB int) (string, error) {
@@ -97,7 +112,7 @@ func (region *SRegion) syncFlavor(name string, cpu, memoryMb, diskGB int) (strin
return flavor.ID, nil
}
}
flavor, err := region.CreateFlavor(name, cpu, memoryMb, 40)
flavor, err := region.CreateFlavor(name, cpu, memoryMb, diskGB)
if err != nil {
return "", errors.Wrap(err, "region.CreateClavor()")
}
+22 -12
View File
@@ -129,6 +129,10 @@ func (host *SHost) GetIStorages() ([]cloudprovider.ICloudStorage, error) {
result := []cloudprovider.ICloudStorage{}
for _, istorage := range istorages {
if istorage.GetStorageType() == api.STORAGE_OPENSTACK_NOVA {
result = append(result, istorage)
continue
}
if storage := istorage.(*SStorage); len(storage.ExtraSpecs.VolumeBackendName) > 0 {
for _, pool := range schedulerPools {
if strings.HasPrefix(pool.Name, fmt.Sprintf("%s@%s", host.GetName(), storage.ExtraSpecs.VolumeBackendName)) {
@@ -197,34 +201,40 @@ func (host *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudpr
return nil, err
}
storage, err := host.zone.getStorageByCategory(desc.SysDisk.StorageType)
if err != nil {
return nil, err
}
sysDiskSizeGB := image.Size / 1024 / 1024 / 1024
if desc.SysDisk.SizeGB < sysDiskSizeGB {
desc.SysDisk.SizeGB = sysDiskSizeGB
}
_sysDisk, err := host.zone.region.CreateDisk(desc.ExternalImageId, storage.Name, "", desc.SysDisk.SizeGB, desc.SysDisk.Name)
if err != nil {
return nil, err
if desc.SysDisk.SizeGB < image.GetMinOsDiskSizeGb() {
desc.SysDisk.SizeGB = image.GetMinOsDiskSizeGb()
}
BlockDeviceMappingV2 := []map[string]interface{}{
{
BlockDeviceMappingV2 := []map[string]interface{}{}
if desc.SysDisk.StorageType != api.STORAGE_OPENSTACK_NOVA { //新建volume
storage, err := host.zone.getStorageByCategory(desc.SysDisk.StorageType)
if err != nil {
return nil, err
}
_sysDisk, err := host.zone.region.CreateDisk(desc.ExternalImageId, storage.Name, "", desc.SysDisk.SizeGB, desc.SysDisk.Name)
if err != nil {
return nil, err
}
BlockDeviceMappingV2 = append(BlockDeviceMappingV2, map[string]interface{}{
"boot_index": 0,
"uuid": _sysDisk.GetGlobalId(),
"source_type": "volume",
"destination_type": "volume",
"delete_on_termination": true,
},
})
}
var _disk *SDisk
for _, disk := range desc.DataDisks {
storage, err = host.zone.getStorageByCategory(disk.StorageType)
storage, err := host.zone.getStorageByCategory(disk.StorageType)
if err != nil {
break
}
+12 -5
View File
@@ -263,12 +263,16 @@ func (instance *SInstance) GetInstanceType() string {
func (instance *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
disks := []SDisk{}
hasSysDisk := false
for i := 0; i < len(instance.VolumesAttached); i++ {
disk, err := instance.host.zone.region.GetDisk(instance.VolumesAttached[i].ID)
if err != nil {
return nil, err
}
disks = append(disks, *disk)
if disk.Bootable {
hasSysDisk = true
}
}
iDisks := []cloudprovider.ICloudDisk{}
for i := 0; i < len(disks); i++ {
@@ -279,6 +283,13 @@ func (instance *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
disks[i].storage = store
iDisks = append(iDisks, &disks[i])
}
if !hasSysDisk {
nova := SNovaStorage{zone: instance.host.zone}
sysDisk, _ := nova.GetIDiskById(instance.ID)
iDisks = append([]cloudprovider.ICloudDisk{sysDisk}, iDisks...)
}
return iDisks, nil
}
@@ -432,11 +443,7 @@ func (instance *SInstance) DeployVM(ctx context.Context, name string, username s
}
func (instance *SInstance) RebuildRoot(ctx context.Context, imageId string, passwd string, publicKey string, sysSizeGB int) (string, error) {
sysDiskId := ""
if len(instance.VolumesAttached) > 0 {
sysDiskId = instance.VolumesAttached[0].ID
}
return sysDiskId, instance.host.zone.region.ReplaceSystemDisk(instance.ID, imageId, passwd, publicKey, sysSizeGB)
return instance.ID, instance.host.zone.region.ReplaceSystemDisk(instance.ID, imageId, passwd, publicKey, sysSizeGB)
}
func (instance *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error {
+120
View File
@@ -0,0 +1,120 @@
// 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 openstack
import (
"fmt"
"yunion.io/x/jsonutils"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudprovider"
)
type SNovaStorage struct {
zone *SZone
}
func (storage *SNovaStorage) GetMetadata() *jsonutils.JSONDict {
return nil
}
func (storage *SNovaStorage) GetId() string {
return fmt.Sprintf("%s-%s", storage.zone.GetId(), storage.GetName())
}
func (storage *SNovaStorage) GetName() string {
return api.STORAGE_OPENSTACK_NOVA
}
func (storage *SNovaStorage) GetGlobalId() string {
return storage.GetId()
}
func (storage *SNovaStorage) IsEmulated() bool {
return true
}
func (storage *SNovaStorage) GetIZone() cloudprovider.ICloudZone {
return storage.zone
}
func (storage *SNovaStorage) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
return []cloudprovider.ICloudDisk{}, nil
}
func (storage *SNovaStorage) GetStorageType() string {
return api.STORAGE_OPENSTACK_NOVA
}
func (storage *SNovaStorage) GetMediumType() string {
return api.DISK_TYPE_ROTATE
}
func (storage *SNovaStorage) GetCapacityMB() int64 {
return 1000000000
}
func (storage *SNovaStorage) GetStorageConf() jsonutils.JSONObject {
conf := jsonutils.NewDict()
return conf
}
func (storage *SNovaStorage) GetStatus() string {
return api.STORAGE_ONLINE
}
func (storage *SNovaStorage) Refresh() error {
// do nothing
return nil
}
func (storage *SNovaStorage) GetEnabled() bool {
return true
}
func (storage *SNovaStorage) GetIStoragecache() cloudprovider.ICloudStoragecache {
return storage.zone.region.getStoragecache()
}
func (storage *SNovaStorage) CreateIDisk(name string, sizeGb int, desc string) (cloudprovider.ICloudDisk, error) {
return nil, cloudprovider.ErrNotSupported
}
func (storage *SNovaStorage) GetIDiskById(idStr string) (cloudprovider.ICloudDisk, error) {
instance, err := storage.zone.region.GetInstance(idStr)
if err != nil {
return nil, err
}
disk := SDisk{
ID: instance.ID,
Name: fmt.Sprintf("root disk for %s", instance.Name),
Size: instance.Flavor.Disk,
Status: DISK_STATUS_IN_USE,
Bootable: true,
CreatedAt: instance.Created,
VolumeType: api.STORAGE_OPENSTACK_NOVA,
}
disk.nova = storage
return &disk, nil
}
func (storage *SNovaStorage) GetMountPoint() string {
return ""
}
func (storage *SNovaStorage) IsSysDiskStore() bool {
return true
}
+2 -1
View File
@@ -21,6 +21,7 @@ import (
"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"
@@ -137,7 +138,7 @@ func (region *SRegion) GetIStorageById(id string) (cloudprovider.ICloudStorage,
istore, err := izones[i].GetIStorageById(id)
if err == nil {
return istore, nil
} else if err != cloudprovider.ErrNotFound {
} else if errors.Cause(err) != cloudprovider.ErrNotFound {
return nil, err
}
}
+12
View File
@@ -30,4 +30,16 @@ func init() {
printList(zones, 0, 0, 0, nil)
return nil
})
type SchedulerPoolListOptions struct {
}
shellutils.R(&SchedulerPoolListOptions{}, "scheduler-pool-list", "List scheduler pool", func(cli *openstack.SRegion, args *SchedulerPoolListOptions) error {
pools, err := cli.GetSchedulerStatsPool()
if err != nil {
return err
}
printList(pools, 0, 0, 0, nil)
return nil
})
}
+23 -12
View File
@@ -21,6 +21,7 @@ import (
"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"
@@ -101,18 +102,26 @@ func (zone *SZone) GetIWires() ([]cloudprovider.ICloudWire, error) {
}
func (zone *SZone) fetchSchedulerStatsPool() error {
zone.schedulerPools = []SPool{}
for _, service := range []string{"volumev3", "volumev2", "volume"} {
_, resp, err := zone.region.List(service, "/scheduler-stats/get_pools", "", nil)
if err == nil {
if err := resp.Unmarshal(&zone.schedulerPools, "pools"); err != nil {
return err
}
return nil
}
log.Warningf("failed to get scheduler-stats pool by service %s error: %v, try another", service, err)
var err error = nil
zone.schedulerPools, err = zone.region.GetSchedulerStatsPool()
if err != nil {
return errors.Wrap(err, "GetSchedulerStatsPool")
}
return fmt.Errorf("failed to find scheduler-stats pool by cinder service")
return nil
}
func (region *SRegion) GetSchedulerStatsPool() ([]SPool, error) {
pools := []SPool{}
for _, service := range []string{"volumev3", "volumev2", "volume"} {
_, resp, err := region.List(service, "/scheduler-stats/get_pools", "", nil)
if err == nil {
if err := resp.Unmarshal(&pools, "pools"); err != nil {
return nil, errors.Wrap(err, "resp.Unmarshal(&pools)")
}
return pools, nil
}
}
return nil, fmt.Errorf("failed to find scheduler-stats pool by cinder service")
}
func (zone *SZone) getSchedulerStatsPool() ([]SPool, error) {
@@ -157,10 +166,12 @@ func (zone *SZone) fetchStorages() error {
storages[i].zone = zone
zone.istorages = append(zone.istorages, &storages[i])
}
return nil
break
}
log.Debugf("failed to get volume types by service %s error: %v, try another", service, err)
}
nova := &SNovaStorage{zone: zone}
zone.istorages = append(zone.istorages, nova)
return fmt.Errorf("failed to find storage types by cinder service")
}