diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index 8b4d9b6544..d19a699e94 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -211,7 +211,7 @@ type ICloudVM interface { GetInstanceType() string AssignSecurityGroup(secgroupId string) error - AssignSecurityGroups(secgroupIds []string) error + SetSecurityGroups(secgroupIds []string) error GetHypervisor() string diff --git a/pkg/compute/guestdrivers/managedvirtual.go b/pkg/compute/guestdrivers/managedvirtual.go index 6e45485452..82a5b475a2 100644 --- a/pkg/compute/guestdrivers/managedvirtual.go +++ b/pkg/compute/guestdrivers/managedvirtual.go @@ -461,7 +461,7 @@ func (self *SManagedVirtualizedGuestDriver) RequestSyncConfigOnHost(ctx context. } externalIds = append(externalIds, extID) } - return nil, iVM.AssignSecurityGroups(externalIds) + return nil, iVM.SetSecurityGroups(externalIds) } iDisks, err := iVM.GetIDisks() diff --git a/pkg/compute/guestdrivers/openstack.go b/pkg/compute/guestdrivers/openstack.go index fd3b75e4dc..0c54fd3711 100644 --- a/pkg/compute/guestdrivers/openstack.go +++ b/pkg/compute/guestdrivers/openstack.go @@ -1,8 +1,14 @@ package guestdrivers import ( + "context" + + "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/compute/options" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/billing" ) type SOpenStackGuestDriver struct { @@ -29,3 +35,59 @@ func (self *SOpenStackGuestDriver) GetDefaultSysDiskBackend() string { func (self *SOpenStackGuestDriver) GetMinimalSysDiskSizeGb() int { return options.Options.DefaultDiskSizeMB / 1024 } + +func (self *SOpenStackGuestDriver) ChooseHostStorage(host *models.SHost, backend string) *models.SStorage { + storages := host.GetAttachedStorages("") + for i := 0; i < len(storages); i++ { + if storages[i].StorageType == backend { + return &storages[i] + } + } + for _, stype := range []string{models.STORAGE_OPENSTACK_ISCSI} { + for i := 0; i < len(storages); i++ { + if storages[i].StorageType == stype { + return &storages[i] + } + } + } + return nil +} + +func (self *SOpenStackGuestDriver) GetDetachDiskStatus() ([]string, error) { + return []string{models.VM_READY}, nil +} + +func (self *SOpenStackGuestDriver) GetAttachDiskStatus() ([]string, error) { + return []string{models.VM_READY}, nil +} + +func (self *SOpenStackGuestDriver) GetRebuildRootStatus() ([]string, error) { + return []string{models.VM_READY, models.VM_RUNNING, models.VM_REBUILD_ROOT_FAIL}, nil +} + +func (self *SOpenStackGuestDriver) GetChangeConfigStatus() ([]string, error) { + return []string{models.VM_READY, models.VM_RUNNING}, nil +} + +func (self *SOpenStackGuestDriver) GetDeployStatus() ([]string, error) { + return []string{models.VM_READY, models.VM_RUNNING}, nil +} + +func (self *SOpenStackGuestDriver) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + data, err := self.SManagedVirtualizedGuestDriver.ValidateCreateData(ctx, userCred, data) + if err != nil { + return nil, err + } + if data.Contains("net.0") && data.Contains("net.1") { + return nil, httperrors.NewInputParameterError("cannot support more than 1 nic") + } + return data, nil +} + +func (self *SOpenStackGuestDriver) AllowReconfigGuest() bool { + return true +} + +func (self *SOpenStackGuestDriver) IsSupportedBillingCycle(bc billing.SBillingCycle) bool { + return false +} diff --git a/pkg/compute/guestdrivers/qcloud.go b/pkg/compute/guestdrivers/qcloud.go index 93f92215b3..43494cf286 100644 --- a/pkg/compute/guestdrivers/qcloud.go +++ b/pkg/compute/guestdrivers/qcloud.go @@ -350,7 +350,7 @@ func (self *SQcloudGuestDriver) RequestSyncConfigOnHost(ctx context.Context, gue } externalIds = append(externalIds, extID) } - return nil, iVM.AssignSecurityGroups(externalIds) + return nil, iVM.SetSecurityGroups(externalIds) } iDisks, err := iVM.GetIDisks() diff --git a/pkg/compute/hostdrivers/openstack.go b/pkg/compute/hostdrivers/openstack.go index 973e4a6cfd..cde5ccddf7 100644 --- a/pkg/compute/hostdrivers/openstack.go +++ b/pkg/compute/hostdrivers/openstack.go @@ -16,3 +16,7 @@ func init() { func (self *SOpenStackHostDriver) GetHostType() string { return models.HOST_TYPE_OPENSTACK } + +func (self *SOpenStackHostDriver) ValidateDiskSize(storage *models.SStorage, sizeGb int) error { + return nil +} diff --git a/pkg/util/aliyun/instance.go b/pkg/util/aliyun/instance.go index 6e2b529fdc..5e0f44448a 100644 --- a/pkg/util/aliyun/instance.go +++ b/pkg/util/aliyun/instance.go @@ -896,8 +896,8 @@ func (self *SInstance) AssignSecurityGroup(secgroupId string) error { return self.host.zone.region.AssignSecurityGroup(secgroupId, self.InstanceId) } -func (self *SInstance) AssignSecurityGroups(secgroupIds []string) error { - return self.host.zone.region.AssignSecurityGroups(secgroupIds, self.InstanceId) +func (self *SInstance) SetSecurityGroups(secgroupIds []string) error { + return self.host.zone.region.SetSecurityGroups(secgroupIds, self.InstanceId) } func (self *SInstance) GetBillingType() string { diff --git a/pkg/util/aliyun/securitygroup.go b/pkg/util/aliyun/securitygroup.go index 7385b5d487..458474ada9 100644 --- a/pkg/util/aliyun/securitygroup.go +++ b/pkg/util/aliyun/securitygroup.go @@ -501,10 +501,10 @@ func (self *SRegion) syncSecgroupRules(secgroupId string, rules []secrules.Secur } func (self *SRegion) AssignSecurityGroup(secgroupId, instanceId string) error { - return self.AssignSecurityGroups([]string{secgroupId}, instanceId) + return self.SetSecurityGroups([]string{secgroupId}, instanceId) } -func (self *SRegion) AssignSecurityGroups(secgroupIds []string, instanceId string) error { +func (self *SRegion) SetSecurityGroups(secgroupIds []string, instanceId string) error { params := map[string]string{"InstanceId": instanceId} for _, secgroupId := range secgroupIds { params["SecurityGroupId"] = secgroupId diff --git a/pkg/util/aws/instance.go b/pkg/util/aws/instance.go index 5a0e73a4fd..5b20e7f774 100644 --- a/pkg/util/aws/instance.go +++ b/pkg/util/aws/instance.go @@ -283,10 +283,10 @@ func (self *SInstance) GetMachine() string { } func (self *SInstance) AssignSecurityGroup(secgroupId string) error { - return self.AssignSecurityGroups([]string{secgroupId}) + return self.SetSecurityGroups([]string{secgroupId}) } -func (self *SInstance) AssignSecurityGroups(secgroupIds []string) error { +func (self *SInstance) SetSecurityGroups(secgroupIds []string) error { ids := []*string{} for i := 0; i < len(secgroupIds); i++ { ids = append(ids, &secgroupIds[i]) diff --git a/pkg/util/azure/classic_instance.go b/pkg/util/azure/classic_instance.go index 4f7add3332..bd3fb44378 100644 --- a/pkg/util/azure/classic_instance.go +++ b/pkg/util/azure/classic_instance.go @@ -487,7 +487,7 @@ type assignProperties struct { NetworkSecurityGroup SubResource `json:"networkSecurityGroup,omitempty"` } -func (self *SClassicInstance) AssignSecurityGroups(secgroupIds []string) error { +func (self *SClassicInstance) SetSecurityGroups(secgroupIds []string) error { return cloudprovider.ErrNotSupported } diff --git a/pkg/util/azure/instance.go b/pkg/util/azure/instance.go index 38e07c1f50..663cfd8e5c 100644 --- a/pkg/util/azure/instance.go +++ b/pkg/util/azure/instance.go @@ -1020,7 +1020,7 @@ func (self *SInstance) AssignSecurityGroup(secgroupId string) error { return self.host.zone.region.AssiginSecurityGroup(self.ID, secgroupId) } -func (self *SInstance) AssignSecurityGroups(secgroupIds []string) error { +func (self *SInstance) SetSecurityGroups(secgroupIds []string) error { return cloudprovider.ErrNotSupported } diff --git a/pkg/util/esxi/virtualmachine.go b/pkg/util/esxi/virtualmachine.go index bbe9058d51..6ccdad303a 100644 --- a/pkg/util/esxi/virtualmachine.go +++ b/pkg/util/esxi/virtualmachine.go @@ -528,7 +528,7 @@ func (dc *SVirtualMachine) ChangeConfig2(ctx context.Context, instanceType strin return cloudprovider.ErrNotImplemented } -func (self *SVirtualMachine) AssignSecurityGroups(secgroupIds []string) error { +func (self *SVirtualMachine) SetSecurityGroups(secgroupIds []string) error { return cloudprovider.ErrNotImplemented } diff --git a/pkg/util/huawei/instance.go b/pkg/util/huawei/instance.go index 855f07fe21..61a3a6615c 100644 --- a/pkg/util/huawei/instance.go +++ b/pkg/util/huawei/instance.go @@ -9,6 +9,7 @@ import ( "strconv" "sort" + "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" @@ -363,10 +364,10 @@ func (self *SInstance) GetMachine() string { } func (self *SInstance) AssignSecurityGroup(secgroupId string) error { - return self.AssignSecurityGroups([]string{secgroupId}) + return self.SetSecurityGroups([]string{secgroupId}) } -func (self *SInstance) AssignSecurityGroups(secgroupIds []string) error { +func (self *SInstance) SetSecurityGroups(secgroupIds []string) error { currentSecgroups, err := self.host.zone.region.GetInstanceSecrityGroupIds(self.GetId()) if err != nil { return err diff --git a/pkg/util/openstack/disk.go b/pkg/util/openstack/disk.go index 952833b0a4..9237de2828 100644 --- a/pkg/util/openstack/disk.go +++ b/pkg/util/openstack/disk.go @@ -2,9 +2,11 @@ package openstack import ( "context" + "fmt" "time" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" ) @@ -104,7 +106,7 @@ func (disk *SDisk) GetMetadata() *jsonutils.JSONDict { } func (region *SRegion) GetDisks(category string) ([]SDisk, error) { - _, resp, err := region.CinderGet("/volumes/detail", "", nil) + _, resp, err := region.CinderList("/volumes/detail", "", nil) if err != nil { return nil, err } @@ -126,11 +128,11 @@ func (disk *SDisk) GetId() string { } func (disk *SDisk) Delete(ctx context.Context) error { - return cloudprovider.ErrNotImplemented + return disk.storage.zone.region.DeleteDisk(disk.ID) } func (disk *SDisk) Resize(ctx context.Context, sizeMb int64) error { - return cloudprovider.ErrNotImplemented + return disk.storage.zone.region.ResizeDisk(disk.ID, sizeMb) } func (disk *SDisk) GetName() string { @@ -179,8 +181,8 @@ func (disk *SDisk) Refresh() error { return jsonutils.Update(disk, new) } -func (disk *SDisk) ResizeDisk(newSize int64) error { - return disk.storage.zone.region.ResizeDisk(disk.ID, newSize) +func (disk *SDisk) ResizeDisk(sizeMb int64) error { + return disk.storage.zone.region.ResizeDisk(disk.ID, sizeMb) } func (disk *SDisk) GetDiskFormat() string { @@ -226,8 +228,21 @@ func (disk *SDisk) GetMountpoint() string { return "" } -func (disk *SRegion) CreateDisk(zoneId string, category string, name string, sizeGb int, desc string) (string, error) { - return "", cloudprovider.ErrNotImplemented +func (region *SRegion) CreateDisk(zoneName string, category string, name string, sizeGb int, desc string) (string, error) { + params := map[string]map[string]interface{}{ + "volume": { + "size": sizeGb, + "volume_type": category, + "name": name, + "description": desc, + "availability_zone": zoneName, + }, + } + _, resp, err := region.CinderCreate("/volumes", "", jsonutils.Marshal(params)) + if err != nil { + return "", err + } + return resp.GetString("volume", "id") } func (region *SRegion) GetDisk(diskId string) (*SDisk, error) { @@ -239,12 +254,19 @@ func (region *SRegion) GetDisk(diskId string) (*SDisk, error) { return disk, resp.Unmarshal(disk, "volume") } -func (disk *SRegion) DeleteDisk(diskId string) error { - return cloudprovider.ErrNotImplemented +func (region *SRegion) DeleteDisk(diskId string) error { + _, err := region.CinderDelete("/volumes/"+diskId, "") + return err } -func (disk *SRegion) ResizeDisk(diskId string, sizeMb int64) error { - return cloudprovider.ErrNotImplemented +func (region *SRegion) ResizeDisk(diskId string, sizeMb int64) error { + params := map[string]map[string]interface{}{ + "os-extend": { + "new_size": sizeMb / 1024, + }, + } + _, _, err := region.CinderAction(fmt.Sprintf("/volumes/%s/action", diskId), "", jsonutils.Marshal(params)) + return err } func (disk *SRegion) ResetDisk(diskId, snapshotId string) error { @@ -252,7 +274,12 @@ func (disk *SRegion) ResetDisk(diskId, snapshotId string) error { } func (disk *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) { - return nil, cloudprovider.ErrNotImplemented + snapshotId, err := disk.storage.zone.region.CreateSnapshot(disk.ID, name, desc) + if err != nil { + log.Errorf("createSnapshot fail %v", err) + return nil, err + } + return disk.storage.zone.region.GetISnapshotById(snapshotId) } func (disk *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) { @@ -264,7 +291,7 @@ func (disk *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { } func (disk *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) { - return "", disk.storage.zone.region.ResetDisk(disk.ID, snapshotId) + return "", cloudprovider.ErrNotSupported } func (disk *SDisk) GetBillingType() string { @@ -280,9 +307,5 @@ func (disk *SDisk) GetAccessPath() string { } func (disk *SDisk) Rebuild(ctx context.Context) error { - return disk.storage.zone.region.RebuildDisk(disk.ID) -} - -func (region *SRegion) RebuildDisk(diskId string) error { - return cloudprovider.ErrNotImplemented + return cloudprovider.ErrNotSupported } diff --git a/pkg/util/openstack/image.go b/pkg/util/openstack/image.go new file mode 100644 index 0000000000..8f5f979633 --- /dev/null +++ b/pkg/util/openstack/image.go @@ -0,0 +1,231 @@ +package openstack + +import ( + "context" + "net/url" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/pkg/utils" +) + +const ( + QUEUED = "queued" // The Image service reserved an image ID for the image in the catalog but did not yet upload any image data. + SAVING = "saving" // The Image service is in the process of saving the raw data for the image into the backing store. + ACTIVE = "active" // The image is active and ready for consumption in the Image service. + KILLED = "killed" // An image data upload error occurred. + DELETED = "deleted" // The Image service retains information about the image but the image is no longer available for use. + PENDING_DELETE = "pending_delete" // Similar to the deleted status. An image in this state is not recoverable. + DEACTIVATED = "deactivated" // The image data is not available for use. + UPLOADING = "uploading" // Data has been staged as part of the interoperable image import process. It is not yet available for use. (Since Image API 2.6) + IMPORTING = "importing" // The image data is being processed as part of the interoperable image import process, but is not yet available for use. (Since Image API 2.6) +) + +type SImage struct { + storageCache *SStoragecache + + Status string + Name string + Tags []string + ContainerFormat string + CreatedAt time.Time + DiskFormat string + UpdatedAt time.Time + Visibility string + Self string + MinDisk int + Protected bool + ID string + File string + Checksum string + OsHashAlgo string + OsHashValue string + OsHidden bool + Owner string + Size int + MinRAM int + Schema string + VirtualSize int + visibility string +} + +func (region *SRegion) GetImages(name string, status string, imageIds []string) ([]SImage, error) { + params := url.Values{} + if utils.IsInStringArray(status, []string{QUEUED, SAVING, ACTIVE, KILLED, DELETED, PENDING_DELETE, DEACTIVATED, UPLOADING, IMPORTING}) { + params.Add("status", status) + } + if len(name) > 0 { + params.Add("name", name) + } + if len(imageIds) > 0 { + params.Add("id", "in:"+strings.Join(imageIds, ",")) + } + _, resp, err := region.List("image", "/v2/images?"+params.Encode(), "", nil) + if err != nil { + return nil, err + } + images := []SImage{} + return images, resp.Unmarshal(&images, "images") +} + +func (image *SImage) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (image *SImage) GetId() string { + return image.ID +} + +func (image *SImage) GetName() string { + return image.Name +} + +func (image *SImage) IsEmulated() bool { + return false +} + +func (image *SImage) GetGlobalId() string { + return image.ID +} + +func (image *SImage) Delete(ctx context.Context) error { + return image.storageCache.region.DeleteImage(image.ID) +} + +func (image *SImage) GetStatus() string { + switch image.Status { + case QUEUED, SAVING, UPLOADING, IMPORTING: + return models.CACHED_IMAGE_STATUS_CACHING + case ACTIVE: + return models.CACHED_IMAGE_STATUS_READY + case DELETED, DEACTIVATED, PENDING_DELETE, KILLED: + return models.CACHED_IMAGE_STATUS_CACHE_FAILED + default: + return models.CACHED_IMAGE_STATUS_CACHE_FAILED + } +} + +func (image *SImage) GetImageStatus() string { + switch image.Status { + case QUEUED, SAVING, UPLOADING, IMPORTING: + return models.CACHED_IMAGE_STATUS_CACHING + case ACTIVE: + return models.CACHED_IMAGE_STATUS_READY + case DELETED, DEACTIVATED, PENDING_DELETE, KILLED: + return models.CACHED_IMAGE_STATUS_CACHE_FAILED + default: + return models.CACHED_IMAGE_STATUS_CACHE_FAILED + } +} + +func (image *SImage) Refresh() error { + new, err := image.storageCache.region.GetImage(image.ID) + if err != nil { + return err + } + return jsonutils.Update(image, new) +} + +func (image *SImage) GetImageType() string { + switch image.Visibility { + case "public": + return cloudprovider.CachedImageTypeSystem + default: + return cloudprovider.CachedImageTypeCustomized + } +} + +func (image *SImage) GetSize() int64 { + return int64(image.Size) +} + +func (image *SImage) GetOsType() string { + return "Linux" +} + +func (image *SImage) GetOsDist() string { + return "Linux" +} + +func (image *SImage) GetOsVersion() string { + return "" +} + +func (image *SImage) GetOsArch() string { + return "x86_64" +} + +func (image *SImage) GetMinOsDiskSizeGb() int { + return 50 +} + +func (image *SImage) GetImageFormat() string { + return image.DiskFormat +} + +func (image *SImage) GetCreateTime() time.Time { + return image.CreatedAt +} + +func (region *SRegion) GetImage(imageId string) (*SImage, error) { + images, err := region.GetImages("", "", []string{imageId}) + if err != nil { + return nil, err + } + if len(images) == 0 { + return nil, cloudprovider.ErrNotFound + } + return &images[0], nil +} + +func (image *SImage) GetIStoragecache() cloudprovider.ICloudStoragecache { + return image.storageCache +} + +func (region *SRegion) DeleteImage(imageId string) error { + _, err := region.Delete("image", "/v2/images/"+imageId, "") + return err +} + +func (region *SRegion) GetImageStatus(imageId string) (string, error) { + image, err := region.GetImage(imageId) + if err != nil { + return "", err + } + return image.Status, nil +} + +func (region *SRegion) GetImageByName(name string) (*SImage, error) { + images, err := region.GetImages(name, "", []string{}) + if err != nil { + return nil, err + } + if len(images) == 0 { + return nil, cloudprovider.ErrNotFound + } + return &images[0], nil +} + +// func (region *SRegion) ImportImage(name string, osArch, osDist, osVersion string, imageUrl string) (*SImage, error) { +// params, err := region.GetImportImageParams(name, osArch, osDist, osVersion, imageUrl) +// if err != nil { +// return nil, err +// } + +// log.Debugf("Upload image with params %#v", params) + +// if _, err := region.cvmRequest("ImportImage", params); err != nil { +// return nil, err +// } +// for i := 0; i < 8; i++ { +// image, err := region.GetImageByName(name) +// if err == nil { +// return image, nil +// } +// time.Sleep(time.Minute * time.Duration(i)) +// } +// return nil, cloudprovider.ErrNotFound +// } diff --git a/pkg/util/openstack/instance.go b/pkg/util/openstack/instance.go index 523c7b8583..caa0a26502 100644 --- a/pkg/util/openstack/instance.go +++ b/pkg/util/openstack/instance.go @@ -10,6 +10,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/util/billing" + "yunion.io/x/pkg/utils" ) const ( @@ -137,7 +138,7 @@ func (region *SRegion) GetSecurityGroupsByInstance(instanceId string) ([]Securit func (region *SRegion) GetInstances(zoneName string, hostName string) ([]SInstance, error) { _, maxVersion, _ := region.GetVersion("compute") - _, resp, err := region.Get("compute", "/servers/detail", maxVersion, nil) + _, resp, err := region.List("compute", "/servers/detail", maxVersion, nil) if err != nil { return nil, err } @@ -337,11 +338,17 @@ func (instance *SInstance) GetHypervisor() string { } func (instance *SInstance) StartVM(ctx context.Context) error { - return cloudprovider.ErrNotImplemented + if err := instance.host.zone.region.StartVM(instance.ID); err != nil { + return err + } + return cloudprovider.WaitStatus(instance, models.VM_RUNNING, 10*time.Second, 8*time.Minute) } func (instance *SInstance) StopVM(ctx context.Context, isForce bool) error { - return cloudprovider.ErrNotImplemented + if err := instance.host.zone.region.StopVM(instance.ID, isForce); err != nil { + return err + } + return cloudprovider.WaitStatus(instance, models.VM_RUNNING, 10*time.Second, 8*time.Minute) } func (region *SRegion) GetInstanceVNCUrl(instanceId string) (string, error) { @@ -401,20 +408,23 @@ func (region *SRegion) CreateInstance(name string, imageId string, instanceType return "", cloudprovider.ErrNotImplemented } -func (region *SRegion) doStartVM(instanceId string) error { - return cloudprovider.ErrNotImplemented +func (region *SRegion) instanceOperation(instanceId, operate string) error { + params := jsonutils.Marshal(map[string]string{operate: ""}) + _, maxVersion, _ := region.GetVersion("compute") + _, _, err := region.Post("compute", fmt.Sprintf("/servers/%s/action", instanceId), maxVersion, params) + return err } func (region *SRegion) doStopVM(instanceId string, isForce bool) error { - return cloudprovider.ErrNotImplemented + return region.instanceOperation(instanceId, "os-stop") } func (region *SRegion) doDeleteVM(instanceId string) error { - return cloudprovider.ErrNotImplemented + return region.instanceOperation(instanceId, "forceDelete") } func (region *SRegion) StartVM(instanceId string) error { - return cloudprovider.ErrNotImplemented + return region.instanceOperation(instanceId, "os-start") } func (region *SRegion) StopVM(instanceId string, isForce bool) error { @@ -422,7 +432,20 @@ func (region *SRegion) StopVM(instanceId string, isForce bool) error { } func (region *SRegion) DeleteVM(instanceId string) error { - return cloudprovider.ErrNotImplemented + instance, err := region.GetInstance(instanceId) + if err != nil { + if err == cloudprovider.ErrNotFound { + return nil + } + log.Errorf("failed to get instance %s %v", instanceId, err) + return err + } + status := instance.GetStatus() + log.Debugf("Instance status on delete is %s", status) + if status != models.VM_READY { + log.Warningf("DeleteVM: vm status is %s expect %s", status, models.VM_READY) + } + return region.doDeleteVM(instanceId) } func (region *SRegion) DeployVM(instanceId string, name string, password string, keypairName string, deleteKeypair bool, description string) error { @@ -430,7 +453,7 @@ func (region *SRegion) DeployVM(instanceId string, name string, password string, } func (instance *SInstance) DeleteVM(ctx context.Context) error { - return cloudprovider.ErrNotImplemented + return instance.host.zone.region.DeleteVM(instance.ID) } func (region *SRegion) ReplaceSystemDisk(instanceId string, imageId string, passwd string, keypairName string, sysDiskSizeGB int) error { @@ -454,11 +477,55 @@ func (region *SRegion) AttachDisk(instanceId string, diskId string) error { } func (instance *SInstance) AssignSecurityGroup(secgroupId string) error { - return cloudprovider.ErrNotImplemented + secgroup, err := instance.host.zone.region.GetSecurityGroup(secgroupId) + if err != nil { + return err + } + params := map[string]map[string]string{ + "addSecurityGroup": { + "name": secgroup.Name, + }, + } + _, _, err = instance.host.zone.region.Post("compute", fmt.Sprintf("/servers/%s/action", instance.ID), "", jsonutils.Marshal(params)) + return err } -func (instance *SInstance) AssignSecurityGroups(secgroupIds []string) error { - return cloudprovider.ErrNotImplemented +func (instance *SInstance) RevokeSecurityGroup(secgroupId string) error { + secgroup, err := instance.host.zone.region.GetSecurityGroup(secgroupId) + if err != nil { + return err + } + params := map[string]map[string]string{ + "removeSecurityGroup": { + "name": secgroup.Name, + }, + } + _, _, err = instance.host.zone.region.Post("compute", fmt.Sprintf("/servers/%s/action", instance.ID), "", jsonutils.Marshal(params)) + return err +} + +func (instance *SInstance) SetSecurityGroups(secgroupIds []string) error { + secgroups, err := instance.host.zone.region.GetSecurityGroupsByInstance(instance.ID) + if err != nil { + return err + } + originIds := []string{} + for _, secgroup := range secgroups { + if !utils.IsInStringArray(secgroup.ID, secgroupIds) { + if err := instance.RevokeSecurityGroup(secgroup.ID); err != nil { + return err + } + } + originIds = append(originIds, secgroup.ID) + } + for _, secgroupId := range secgroupIds { + if !utils.IsInStringArray(secgroupId, originIds) { + if err := instance.AssignSecurityGroup(secgroupId); err != nil { + return err + } + } + } + return nil } func (instance *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) { diff --git a/pkg/util/openstack/network.go b/pkg/util/openstack/network.go index b7891dc572..43512a7e3a 100644 --- a/pkg/util/openstack/network.go +++ b/pkg/util/openstack/network.go @@ -73,8 +73,9 @@ func (network *SNetwork) Delete() error { return network.wire.zone.region.DeleteNetwork(network.ID) } -func (network *SRegion) DeleteNetwork(networkId string) error { - return cloudprovider.ErrNotImplemented +func (region *SRegion) DeleteNetwork(networkId string) error { + _, err := region.Delete("network", "/v2.0/subnets/"+networkId, "") + return err } func (network *SNetwork) GetIWire() cloudprovider.ICloudWire { @@ -126,7 +127,7 @@ func (region *SRegion) GetNetwork(networkId string) (*SNetwork, error) { } func (region *SRegion) GetNetworks(vpcId string) ([]SNetwork, error) { - _, resp, err := region.Get("network", "/v2.0/subnets", "", nil) + _, resp, err := region.List("network", "/v2.0/subnets", "", nil) if err != nil { return nil, err } @@ -152,6 +153,19 @@ func (network *SNetwork) Refresh() error { return jsonutils.Update(network, new) } -func (network *SRegion) CreateNetwork(zoneId string, vpcId string, name string, cidr string, desc string) (string, error) { - return "", cloudprovider.ErrNotImplemented +func (region *SRegion) CreateNetwork(vpcId string, name string, cidr string, desc string) (string, error) { + params := map[string]map[string]interface{}{ + "subnet": { + "name": name, + "network_id": vpcId, + "cidr": cidr, + "description": desc, + "ip_version": 4, + }, + } + _, resp, err := region.Post("network", "/v2.0/subnets", "", jsonutils.Marshal(params)) + if err != nil { + return "", err + } + return resp.GetString("subnet", "id") } diff --git a/pkg/util/openstack/openstack.go b/pkg/util/openstack/openstack.go index 8dd02d8b7f..228ba284aa 100644 --- a/pkg/util/openstack/openstack.go +++ b/pkg/util/openstack/openstack.go @@ -3,6 +3,7 @@ package openstack import ( "context" "fmt" + "io" "net/http" "strings" @@ -71,6 +72,31 @@ func (cli *SOpenStackClient) Request(region, service, method string, url string, return session.JSONRequest(service, "", httputils.THttpMethod(method), url, header, body) } +func (cli *SOpenStackClient) RawRequest(region, service, method string, url string, microversion string, body jsonutils.JSONObject) (*http.Response, error) { + header := http.Header{} + if len(microversion) > 0 { + header.Set("X-Openstack-Nova-API-Version", microversion) + } + ctx := context.Background() + session := cli.client.NewSession(ctx, region, "", "internal", cli.tokenCredential, "") + data := strings.NewReader("") + if body != nil { + data = strings.NewReader(body.String()) + } + return session.RawRequest(service, "", httputils.THttpMethod(method), url, header, data) +} + +func (cli *SOpenStackClient) StreamRequest(region, service, method string, url string, microversion string, body io.Reader) (*http.Response, error) { + header := http.Header{} + if len(microversion) > 0 { + header.Set("X-Openstack-Nova-API-Version", microversion) + } + header.Set("Content-Type", "application/octet-stream") + ctx := context.Background() + session := cli.client.NewSession(ctx, region, "", "internal", cli.tokenCredential, "") + return session.RawRequest(service, "", httputils.THttpMethod(method), url, header, body) +} + func (cli *SOpenStackClient) getVersion(region string, service string) (string, string, error) { ctx := context.Background() session := cli.client.NewSession(ctx, region, "", "internal", cli.tokenCredential, "") diff --git a/pkg/util/openstack/port.go b/pkg/util/openstack/port.go index 13b8923d2e..68841312fb 100644 --- a/pkg/util/openstack/port.go +++ b/pkg/util/openstack/port.go @@ -60,7 +60,7 @@ func (region *SRegion) GetPorts(macAddress string) ([]SPort, error) { params.Set("mac_address", macAddress) } url := fmt.Sprintf("%s?%s", base, params.Encode()) - _, resp, err := region.Get("network", url, "", nil) + _, resp, err := region.List("network", url, "", nil) if err != nil { return nil, err } diff --git a/pkg/util/openstack/region.go b/pkg/util/openstack/region.go index adfedb0131..72515dbdcf 100644 --- a/pkg/util/openstack/region.go +++ b/pkg/util/openstack/region.go @@ -3,6 +3,7 @@ package openstack import ( "fmt" "net/http" + "strings" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -63,7 +64,25 @@ func (region *SRegion) Refresh() error { } func (region *SRegion) CreateIVpc(name string, desc string, cidr string) (cloudprovider.ICloudVpc, error) { - return nil, cloudprovider.ErrNotImplemented + params := map[string]map[string]string{ + "network": { + "name": name, + "description": desc, + }, + } + _, resp, err := region.Post("network", "/v2.0/networks", "", jsonutils.Marshal(params)) + if err != nil { + return nil, err + } + err = region.fetchInfrastructure() + if err != nil { + return nil, err + } + vpcId, err := resp.GetString("network", "id") + if err != nil { + return nil, err + } + return region.GetIVpcById(vpcId) } func (region *SRegion) GetIHostById(id string) (cloudprovider.ICloudHost, error) { @@ -173,7 +192,7 @@ func (region *SRegion) GetIZoneById(id string) (cloudprovider.ICloudZone, error) } func (region *SRegion) fetchZones() error { - _, resp, err := region.Get("compute", "/os-availability-zone", "", jsonutils.NewDict()) + _, resp, err := region.List("compute", "/os-availability-zone", "", jsonutils.NewDict()) if err != nil { return err } @@ -227,14 +246,53 @@ func (region *SRegion) fetchInfrastructure() error { } func (region *SRegion) Get(service, url string, microversion string, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) { - return region.client.Request(region.Name, service, "GET", url, microversion, body) + if strings.HasSuffix(url, "/") { + return nil, nil, cloudprovider.ErrNotFound + } + header, resp, err := region.client.Request(region.Name, service, "GET", url, microversion, body) + if err != nil { + if strings.Contains(err.Error(), "404") { + return nil, nil, cloudprovider.ErrNotFound + } + return nil, nil, err + } + return header, resp, nil +} + +func (region *SRegion) List(service, url string, microversion string, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) { + header, resp, err := region.client.Request(region.Name, service, "GET", url, microversion, body) + if err != nil { + if strings.Contains(err.Error(), "404") { + return nil, nil, cloudprovider.ErrNotFound + } + return nil, nil, err + } + return header, resp, nil } func (region *SRegion) Post(service, url string, microversion string, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) { return region.client.Request(region.Name, service, "POST", url, microversion, body) } +func (region *SRegion) Delete(service, url string, microversion string) (*http.Response, error) { + return region.client.RawRequest(region.Name, service, "DELETE", url, microversion, nil) +} + +func (region *SRegion) CinderList(url string, microversion string, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) { + for _, service := range []string{"volumev3", "volumev2", "volume"} { + header, resp, err := region.Get(service, url, microversion, body) + if err == nil { + return header, resp, nil + } + log.Debugf("failed to list %s by service %s error: %v, try another", url, service, err) + } + return nil, nil, fmt.Errorf("failed to get %s by cinder service", url) +} + func (region *SRegion) CinderGet(url string, microversion string, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) { + if strings.HasSuffix(url, "/") { + return nil, nil, cloudprovider.ErrNotFound + } for _, service := range []string{"volumev3", "volumev2", "volume"} { header, resp, err := region.Get(service, url, microversion, body) if err == nil { @@ -245,6 +303,42 @@ func (region *SRegion) CinderGet(url string, microversion string, body jsonutils return nil, nil, fmt.Errorf("failed to get %s by cinder service", url) } +func (region *SRegion) CinderCreate(url string, microversion string, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) { + for _, service := range []string{"volumev3", "volumev2", "volume"} { + header, resp, err := region.Post(service, url, microversion, body) + if err == nil { + return header, resp, nil + } + log.Debugf("failed to create %s by service %s error: %v, try another", url, service, err) + } + return nil, nil, fmt.Errorf("failed to create %s by cinder service", url) +} + +func (region *SRegion) CinderDelete(url string, microversion string) (*http.Response, error) { + if strings.HasSuffix(url, "/") { + return nil, cloudprovider.ErrNotFound + } + for _, service := range []string{"volumev3", "volumev2", "volume"} { + resp, err := region.Delete(service, url, microversion) + if err == nil { + return resp, nil + } + log.Debugf("failed to delete %s by service %s error: %v, try another", url, service, err) + } + return nil, fmt.Errorf("failed to delete %s by cinder service", url) +} + +func (region *SRegion) CinderAction(url string, microversion string, body jsonutils.JSONObject) (http.Header, jsonutils.JSONObject, error) { + for _, service := range []string{"volumev3", "volumev2", "volume"} { + header, resp, err := region.Post(service, url, microversion, body) + if err == nil { + return header, resp, nil + } + log.Debugf("failed to operate %s by service %s error: %v, try another", url, service, err) + } + return nil, nil, fmt.Errorf("failed to operate %s by cinder service", url) +} + func (region *SRegion) ProjectId() string { return region.client.tokenCredential.GetProjectId() } diff --git a/pkg/util/openstack/securitygroup.go b/pkg/util/openstack/securitygroup.go index f737a7c6e8..4540bd764e 100644 --- a/pkg/util/openstack/securitygroup.go +++ b/pkg/util/openstack/securitygroup.go @@ -2,10 +2,12 @@ package openstack import ( "net" + "sort" "strings" "time" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/pkg/util/secrules" "yunion.io/x/pkg/utils" @@ -45,6 +47,20 @@ type SSecurityGroup struct { TenantID string } +type SecurigyGroupRuleSet []SSecurityGroupRule + +func (v SecurigyGroupRuleSet) Len() int { + return len(v) +} + +func (v SecurigyGroupRuleSet) Swap(i, j int) { + v[i], v[j] = v[j], v[i] +} + +func (v SecurigyGroupRuleSet) Less(i, j int) bool { + return strings.Compare(v[i].String(), v[j].String()) <= 0 +} + func (region *SRegion) GetSecurityGroup(secgroupId string) (*SSecurityGroup, error) { _, resp, err := region.Get("network", "/v2.0/security-groups/"+secgroupId, "", nil) if err != nil { @@ -55,7 +71,7 @@ func (region *SRegion) GetSecurityGroup(secgroupId string) (*SSecurityGroup, err } func (region *SRegion) GetSecurityGroups() ([]SSecurityGroup, error) { - _, resp, err := region.Get("network", "/v2.0/security-groups", "", nil) + _, resp, err := region.List("network", "/v2.0/security-groups", "", nil) if err != nil { return nil, err } @@ -172,14 +188,127 @@ func (secgroup *SSecurityGroup) Refresh() error { return jsonutils.Update(secgroup, new) } -func (secgroup *SRegion) SyncSecurityGroup(secgroupId string, vpcId string, name string, desc string, rules []secrules.SecurityRule) (string, error) { - return "", cloudprovider.ErrNotImplemented +func (region *SRegion) SyncSecurityGroup(secgroupId string, vpcId string, name string, desc string, rules []secrules.SecurityRule) (string, error) { + if len(secgroupId) > 0 { + _, err := region.GetSecurityGroup(secgroupId) + if err != nil { + if err != cloudprovider.ErrNotFound { + return "", err + } + secgroupId = "" + } + } + if len(secgroupId) == 0 { + secgroupId, err := region.CreateSecurityGroup(name, desc) + if err != nil { + return "", err + } + secgroupId = secgroupId + } + return region.syncSecgroupRules(secgroupId, rules) +} + +func (region *SRegion) syncSecgroupRules(secgroupId string, rules []secrules.SecurityRule) (string, error) { + secgroup, err := region.GetSecurityGroup(secgroupId) + if err != nil { + return "", err + } + + sort.Sort(secrules.SecurityRuleSet(rules)) + sort.Sort(SecurigyGroupRuleSet(secgroup.SecurityGroupRules)) + + i, j := 0, 0 + for i < len(rules) || j < len(secgroup.SecurityGroupRules) { + if i < len(rules) && j < len(secgroup.SecurityGroupRules) { + secruleStr := secgroup.SecurityGroupRules[j].String() + ruleStr := rules[i].String() + cmp := strings.Compare(secruleStr, ruleStr) + if cmp == 0 { + i++ + j++ + } else if cmp > 0 { + if err := region.delSecurityGroupRule(secgroup.SecurityGroupRules[j].ID); err != nil { + log.Errorf("delSecurityGroupRule error %v", err) + return "", err + } + j++ + } else { + if err := region.addSecurityGroupRules(secgroupId, &rules[i]); err != nil { + log.Errorf("addSecurityGroupRule error %v", rules[i]) + return "", err + } + i++ + } + } else if i >= len(rules) { + if err := region.delSecurityGroupRule(secgroup.SecurityGroupRules[j].ID); err != nil { + log.Errorf("delSecurityGroupRule error %v", err) + return "", err + } + j++ + } else if j >= len(secgroup.SecurityGroupRules) { + if err := region.addSecurityGroupRules(secgroupId, &rules[i]); err != nil { + log.Errorf("addSecurityGroupRule error %v", rules[i]) + return "", err + } + i++ + } + } + + return secgroupId, nil +} + +func (region *SRegion) delSecurityGroupRule(ruleId string) error { + _, err := region.Delete("network", "/v2.0/security-group-rules/"+ruleId, "") + return err +} + +func (region *SRegion) addSecurityGroupRules(secgroupId string, rule *secrules.SecurityRule) error { + direction := "ingress" + if rule.Direction == secrules.SecurityRuleEgress { + direction = "egress" + } + params := map[string]map[string]interface{}{ + "security_group_rule": { + "direction": direction, + "protocol": rule.Protocol, + "security_group_id": secgroupId, + "remote_ip_prefix": rule.IPNet.String(), + }, + } + if len(rule.Ports) > 0 { + for _, port := range rule.Ports { + params["security_group_rule"]["port_range_max"] = port + params["security_group_rule"]["port_range_max"] = port + _, _, err := region.Post("network", "/v2.0/security-group-rules", "", jsonutils.Marshal(params)) + if err != nil { + return err + } + } + return nil + } + if rule.PortEnd > 0 && rule.PortStart > 0 { + params["security_group_rule"]["port_range_max"] = rule.PortStart + params["security_group_rule"]["port_range_max"] = rule.PortEnd + } + _, _, err := region.Post("network", "/v2.0/security-group-rules", "", jsonutils.Marshal(params)) + return err } func (region *SRegion) DeleteSecurityGroup(vpcId, secGroupId string) error { - return cloudprovider.ErrNotImplemented + _, err := region.Delete("network", "/v2.0/security-groups/"+secGroupId, "") + return err } -func (region *SRegion) CreateSecurityGroup(name, description string) (*SSecurityGroup, error) { - return nil, cloudprovider.ErrNotImplemented +func (region *SRegion) CreateSecurityGroup(name, description string) (string, error) { + params := map[string]map[string]interface{}{ + "security_group": { + "name": name, + "description": description, + }, + } + _, resp, err := region.Post("network", "/v2.0/security-groups", "", jsonutils.Marshal(params)) + if err != nil { + return "", err + } + return resp.GetString("security_group", "id") } diff --git a/pkg/util/openstack/shell/disk.go b/pkg/util/openstack/shell/disk.go index fd66d361c0..b521384c6c 100644 --- a/pkg/util/openstack/shell/disk.go +++ b/pkg/util/openstack/shell/disk.go @@ -1,6 +1,8 @@ package shell import ( + "fmt" + "yunion.io/x/onecloud/pkg/util/openstack" "yunion.io/x/onecloud/pkg/util/shellutils" ) @@ -18,11 +20,11 @@ func init() { return nil }) - type DiskShowOptions struct { - ID string `help:"Storage type for disk"` + type DiskOptions struct { + ID string `help:"ID of disk"` } - shellutils.R(&DiskShowOptions{}, "disk-show", "Show disk", func(cli *openstack.SRegion, args *DiskShowOptions) error { + shellutils.R(&DiskOptions{}, "disk-show", "Show disk", func(cli *openstack.SRegion, args *DiskOptions) error { disk, err := cli.GetDisk(args.ID) if err != nil { return err @@ -30,4 +32,23 @@ func init() { printObject(disk) return nil }) + + shellutils.R(&DiskOptions{}, "disk-delete", "Delete disk", func(cli *openstack.SRegion, args *DiskOptions) error { + return cli.DeleteDisk(args.ID) + }) + + type DiskCreateOptions struct { + ZONE string `help:"Zone name"` + CATEGORY string `help:"Disk category"` + NAME string `help:"Disk Name"` + SIZE int `help:"Disk Size GB"` + } + shellutils.R(&DiskCreateOptions{}, "disk-create", "Create disk", func(cli *openstack.SRegion, args *DiskCreateOptions) error { + diskId, err := cli.CreateDisk(args.ZONE, args.CATEGORY, args.NAME, args.SIZE, "") + if err != nil { + return err + } + fmt.Println(diskId) + return nil + }) } diff --git a/pkg/util/openstack/shell/image.go b/pkg/util/openstack/shell/image.go new file mode 100644 index 0000000000..da676776a0 --- /dev/null +++ b/pkg/util/openstack/shell/image.go @@ -0,0 +1,36 @@ +package shell + +import ( + "yunion.io/x/onecloud/pkg/util/openstack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ImageListOptions struct { + Name string + Ids []string + Status string + } + shellutils.R(&ImageListOptions{}, "image-list", "List images", func(cli *openstack.SRegion, args *ImageListOptions) error { + images, err := cli.GetImages(args.Name, args.Status, args.Ids) + if err != nil { + return err + } + printList(images, 0, 0, 0, []string{}) + return nil + }) + + type ImageOptions struct { + ID string + } + + shellutils.R(&ImageOptions{}, "image-show", "Show image", func(cli *openstack.SRegion, args *ImageOptions) error { + image, err := cli.GetImages("", "", []string{args.ID}) + if err != nil { + return err + } + printObject(image[0]) + return nil + }) + +} diff --git a/pkg/util/openstack/shell/snapshot.go b/pkg/util/openstack/shell/snapshot.go index 79097fa691..1b8d007999 100644 --- a/pkg/util/openstack/shell/snapshot.go +++ b/pkg/util/openstack/shell/snapshot.go @@ -1,6 +1,8 @@ package shell import ( + "fmt" + "yunion.io/x/onecloud/pkg/util/openstack" "yunion.io/x/onecloud/pkg/util/shellutils" ) @@ -18,11 +20,11 @@ func init() { return nil }) - type SnapshotShowOptions struct { + type SnapshotOptions struct { ID string `help:"ID of snapshot"` } - shellutils.R(&SnapshotShowOptions{}, "snapshot-show", "Show snapshot", func(cli *openstack.SRegion, args *SnapshotShowOptions) error { + shellutils.R(&SnapshotOptions{}, "snapshot-show", "Show snapshot", func(cli *openstack.SRegion, args *SnapshotOptions) error { snapshot, err := cli.GetISnapshotById(args.ID) if err != nil { return err @@ -31,4 +33,23 @@ func init() { return nil }) + shellutils.R(&SnapshotOptions{}, "snapshot-delete", "Delete snapshot", func(cli *openstack.SRegion, args *SnapshotOptions) error { + return cli.DeleteSnapshot(args.ID) + }) + + type SnapshotCreateOptions struct { + DISKID string `help:"Disk ID"` + Name string `help:"Disk Name"` + Desc string `help:"Disk description"` + } + + shellutils.R(&SnapshotCreateOptions{}, "snapshot-create", "Create snapshot", func(cli *openstack.SRegion, args *SnapshotCreateOptions) error { + snapshotId, err := cli.CreateSnapshot(args.DISKID, args.Name, args.Desc) + if err != nil { + return err + } + fmt.Println(snapshotId) + return nil + }) + } diff --git a/pkg/util/openstack/snapshot.go b/pkg/util/openstack/snapshot.go index 090aa76d94..c31ce706c4 100644 --- a/pkg/util/openstack/snapshot.go +++ b/pkg/util/openstack/snapshot.go @@ -78,7 +78,7 @@ func (snapshot *SSnapshot) Refresh() error { } func (region *SRegion) GetSnapshots(diskId string) ([]cloudprovider.ICloudSnapshot, error) { - _, resp, err := region.CinderGet("/snapshots/detail", "", nil) + _, resp, err := region.CinderList("/snapshots/detail", "", nil) if err != nil { return nil, err } @@ -121,6 +121,9 @@ func (snapshot *SSnapshot) GetGlobalId() string { } func (snapshot *SSnapshot) GetName() string { + if len(snapshot.Name) == 0 { + return snapshot.ID + } return snapshot.Name } @@ -139,10 +142,23 @@ func (snapshot *SSnapshot) GetDiskType() string { return models.DISK_TYPE_DATA } -func (snapshot *SRegion) DeleteSnapshot(snapshotId string) error { - return cloudprovider.ErrNotImplemented +func (region *SRegion) DeleteSnapshot(snapshotId string) error { + _, err := region.CinderDelete("/snapshots/"+snapshotId, "") + return err } -func (snapshot *SRegion) CreateSnapshot(diskId, name, desc string) (string, error) { - return "", cloudprovider.ErrNotImplemented +func (region *SRegion) CreateSnapshot(diskId, name, desc string) (string, error) { + params := map[string]map[string]interface{}{ + "snapshot": { + "volume_id": diskId, + "name": name, + "description": desc, + "force": true, + }, + } + _, resp, err := region.CinderCreate("/snapshots", "", jsonutils.Marshal(params)) + if err != nil { + return "", err + } + return resp.GetString("snapshot", "id") } diff --git a/pkg/util/openstack/storage.go b/pkg/util/openstack/storage.go index d395372473..2fcfafdac1 100644 --- a/pkg/util/openstack/storage.go +++ b/pkg/util/openstack/storage.go @@ -4,6 +4,7 @@ import ( "strings" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" @@ -94,7 +95,12 @@ func (storage *SStorage) GetIStoragecache() cloudprovider.ICloudStoragecache { } func (storage *SStorage) CreateIDisk(name string, sizeGb int, desc string) (cloudprovider.ICloudDisk, error) { - return nil, cloudprovider.ErrNotImplemented + diskId, err := storage.zone.region.CreateDisk(storage.zone.ZoneName, storage.Name, name, sizeGb, desc) + if err != nil { + log.Errorf("createDisk fail %v", err) + return nil, err + } + return storage.GetIDiskById(diskId) } func (storage *SStorage) GetIDiskById(idStr string) (cloudprovider.ICloudDisk, error) { diff --git a/pkg/util/openstack/storagecache.go b/pkg/util/openstack/storagecache.go index 65f5a03a10..711985a5b4 100644 --- a/pkg/util/openstack/storagecache.go +++ b/pkg/util/openstack/storagecache.go @@ -3,11 +3,18 @@ package openstack import ( "context" "fmt" + "time" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/image/options" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/util/qemuimg" ) type SStoragecache struct { @@ -49,7 +56,16 @@ func (cache *SStoragecache) GetManagerId() string { } func (cache *SStoragecache) fetchImages() error { - return cloudprovider.ErrNotImplemented + images, err := cache.region.GetImages("", ACTIVE, []string{}) + if err != nil { + return err + } + cache.iimages = make([]cloudprovider.ICloudImage, len(images)) + for i := 0; i < len(images); i++ { + images[i].storageCache = cache + cache.iimages[i] = &images[i] + } + return nil } func (cache *SStoragecache) GetIImages() ([]cloudprovider.ICloudImage, error) { @@ -62,7 +78,12 @@ func (cache *SStoragecache) GetIImages() ([]cloudprovider.ICloudImage, error) { } func (cache *SStoragecache) GetIImageById(extId string) (cloudprovider.ICloudImage, error) { - return nil, cloudprovider.ErrNotImplemented + image, err := cache.region.GetImage(extId) + if err != nil { + return nil, err + } + image.storageCache = cache + return image, nil } func (cache *SStoragecache) GetPath() string { @@ -70,11 +91,71 @@ func (cache *SStoragecache) GetPath() string { } func (cache *SStoragecache) UploadImage(ctx context.Context, userCred mcclient.TokenCredential, imageId string, osArch, osType, osDist, osVersion string, extId string, isForce bool) (string, error) { - return "", cloudprovider.ErrNotImplemented + if len(extId) > 0 { + log.Debugf("UploadImage: Image external ID exists %s", extId) + + statsu, err := cache.region.GetImageStatus(extId) + if err != nil { + log.Errorf("GetImageStatus error %s", err) + } + if statsu == ACTIVE && !isForce { + return extId, nil + } + } + log.Debugf("UploadImage: no external ID") + return cache.uploadImage(ctx, userCred, imageId, osArch, osType, osDist, osVersion, isForce) } -func (cache *SStoragecache) uploadImage(userCred mcclient.TokenCredential, imageId string, osArch, osType, osDist string, isForce bool) (string, error) { - return "", cloudprovider.ErrNotImplemented +func (cache *SStoragecache) uploadImage(ctx context.Context, userCred mcclient.TokenCredential, imageId string, osArch, osType, osDist, osVersion string, isForce bool) (string, error) { + s := auth.GetAdminSession(ctx, options.Options.Region, "") + + meta, reader, err := modules.Images.Download(s, imageId, string(qemuimg.VMDK), false) + if err != nil { + return "", err + } + log.Infof("meta data %s", meta) + + imageBaseName := imageId + if imageBaseName[0] >= '0' && imageBaseName[0] <= '9' { + imageBaseName = fmt.Sprintf("img%s", imageId) + } + imageName := imageBaseName + nameIdx := 1 + + for { + _, err = cache.region.GetImageByName(imageName) + if err != nil { + if err == cloudprovider.ErrNotFound { + break + } else { + return "", err + } + } + imageName = fmt.Sprintf("%s-%d", imageBaseName, nameIdx) + nameIdx++ + } + + params := map[string]string{ + "container_format": "bare", + "disk_format": "vmdk", + "name": imageName, + "id": imageId, + } + + _, resp, err := cache.region.Post("images", "/v2/images", "", jsonutils.Marshal(params)) + if err != nil { + return "", err + } + image := &SImage{storageCache: cache} + if err := resp.Unmarshal(image); err != nil { + return "", err + } + + _, err = cache.region.client.StreamRequest(cache.region.Name, "image", "PUT", fmt.Sprintf("/v2/images/%s/file", image.ID), "", reader) + if err != nil { + return "", err + } + return image.ID, cloudprovider.WaitStatus(image, models.CACHED_IMAGE_STATUS_READY, 15*time.Second, 3600*time.Second) } func (cache *SStoragecache) CreateIImage(snapshoutId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) { diff --git a/pkg/util/openstack/vpc.go b/pkg/util/openstack/vpc.go index 43e739de86..1ecfab8245 100644 --- a/pkg/util/openstack/vpc.go +++ b/pkg/util/openstack/vpc.go @@ -96,7 +96,8 @@ func (vpc *SVpc) Delete() error { } func (region *SRegion) DeleteVpc(vpcId string) error { - return cloudprovider.ErrNotImplemented + _, err := region.Delete("network", "/v2.0/networks/"+vpcId, "") + return err } func (vpc *SVpc) GetISecurityGroups() ([]cloudprovider.ICloudSecurityGroup, error) { @@ -191,7 +192,7 @@ func (region *SRegion) GetVpc(vpcId string) (*SVpc, error) { } func (region *SRegion) GetVpcs() ([]SVpc, error) { - _, resp, err := region.Get("network", "/v2.0/networks", "", nil) + _, resp, err := region.List("network", "/v2.0/networks", "", nil) if err != nil { return nil, err } diff --git a/pkg/util/openstack/wire.go b/pkg/util/openstack/wire.go index 8673f2d7dc..b7ffa9d6f1 100644 --- a/pkg/util/openstack/wire.go +++ b/pkg/util/openstack/wire.go @@ -4,6 +4,7 @@ import ( "fmt" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" ) @@ -56,7 +57,13 @@ func (wire *SWire) GetBandwidth() int { } func (wire *SWire) CreateINetwork(name string, cidr string, desc string) (cloudprovider.ICloudNetwork, error) { - return nil, cloudprovider.ErrNotImplemented + networkId, err := wire.zone.region.CreateNetwork(wire.vpc.ID, name, cidr, desc) + if err != nil { + log.Errorf("CreateNetwork error %s", err) + return nil, err + } + wire.inetworks = nil + return wire.GetINetworkById(networkId) } func (wire *SWire) GetINetworkById(netid string) (cloudprovider.ICloudNetwork, error) { diff --git a/pkg/util/openstack/zone.go b/pkg/util/openstack/zone.go index ec3f8af98a..3fe833a560 100644 --- a/pkg/util/openstack/zone.go +++ b/pkg/util/openstack/zone.go @@ -94,7 +94,7 @@ func (zone *SZone) fetchStorages() error { zone.istorages = []cloudprovider.ICloudStorage{} for _, service := range []string{"volumev3", "volumev2", "volume"} { - _, resp, err := zone.region.Get(service, "/types", "", nil) + _, resp, err := zone.region.List(service, "/types", "", nil) if err == nil { storages := []SStorage{} if err := resp.Unmarshal(&storages, "volume_types"); err != nil { @@ -142,7 +142,7 @@ func (zone *SZone) GetIHosts() ([]cloudprovider.ICloudHost, error) { hosts := []SHost{} _, maxVersion, err := zone.region.GetVersion("compute") if err == nil && version.GE(maxVersion, HYPERVISORS_VERSION) { - _, resp, err := zone.region.Get("compute", "/os-hypervisors/detail", maxVersion, nil) + _, resp, err := zone.region.List("compute", "/os-hypervisors/detail", maxVersion, nil) if err != nil { return nil, err } @@ -156,7 +156,7 @@ func (zone *SZone) GetIHosts() ([]cloudprovider.ICloudHost, error) { return ihosts, nil } - _, resp, err := zone.region.Get("compute", "/os-hosts", "", nil) + _, resp, err := zone.region.List("compute", "/os-hosts", "", nil) if err != nil { return nil, err } diff --git a/pkg/util/qcloud/instance.go b/pkg/util/qcloud/instance.go index dd5c841c34..8feea35642 100644 --- a/pkg/util/qcloud/instance.go +++ b/pkg/util/qcloud/instance.go @@ -749,7 +749,7 @@ func (self *SInstance) AssignSecurityGroup(secgroupId string) error { return self.host.zone.region.instanceOperation(self.InstanceId, "ModifyInstancesAttribute", params) } -func (self *SInstance) AssignSecurityGroups(secgroupIds []string) error { +func (self *SInstance) SetSecurityGroups(secgroupIds []string) error { params := map[string]string{} for i := 0; i < len(secgroupIds); i++ { params[fmt.Sprintf("SecurityGroups.%d", i)] = secgroupIds[i]