diff --git a/Gopkg.lock b/Gopkg.lock index cecb4eeb5e..23446f995b 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1765,7 +1765,7 @@ [[projects]] branch = "master" - digest = "1:0fbe21d28e25757df5cfc5266c9c8d8183da6b1749dd20dfe06a94ba801db4fe" + digest = "1:126c297dd61405797b67233b92fa057489cc10323468d413fffba72fd1f64211" name = "yunion.io/x/pkg" packages = [ "gotypes", @@ -1799,7 +1799,11 @@ "utils", ] pruneopts = "UT" +<<<<<<< HEAD revision = "740ce1e70d2417c195f669bbddf8ade8ec967419" +======= + revision = "91e7c6f49631cea6aa6b65d6d3e9e779f13ebc1e" +>>>>>>> 细化存储信息 [[projects]] branch = "master" diff --git a/pkg/apis/compute/host_const.go b/pkg/apis/compute/host_const.go index a00f009e8c..099f25d971 100644 --- a/pkg/apis/compute/host_const.go +++ b/pkg/apis/compute/host_const.go @@ -75,6 +75,19 @@ const ( HostResourceTypeDedicated = "dedicated" ) -var HOST_TYPES = []string{HOST_TYPE_BAREMETAL, HOST_TYPE_HYPERVISOR, HOST_TYPE_ESXI, HOST_TYPE_KUBELET, HOST_TYPE_XEN, HOST_TYPE_ALIYUN, HOST_TYPE_AZURE, HOST_TYPE_AWS, HOST_TYPE_QCLOUD, HOST_TYPE_HUAWEI, HOST_TYPE_OPENSTACK, HOST_TYPE_UCLOUD} +var HOST_TYPES = []string{ + HOST_TYPE_BAREMETAL, + HOST_TYPE_HYPERVISOR, + HOST_TYPE_ESXI, + HOST_TYPE_KUBELET, + HOST_TYPE_XEN, + HOST_TYPE_ALIYUN, + HOST_TYPE_AZURE, + HOST_TYPE_AWS, + HOST_TYPE_QCLOUD, + HOST_TYPE_HUAWEI, + HOST_TYPE_OPENSTACK, + HOST_TYPE_ZSTACK, +} var NIC_TYPES = []string{NIC_TYPE_IPMI, NIC_TYPE_ADMIN} diff --git a/pkg/apis/compute/storage_const.go b/pkg/apis/compute/storage_const.go index cb7eed9ade..83aa529dcd 100644 --- a/pkg/apis/compute/storage_const.go +++ b/pkg/apis/compute/storage_const.go @@ -63,6 +63,12 @@ const ( STORAGE_UCLOUD_LOCAL_NORMAL = "LOCAL_NORMAL" // 普通本地盘 STORAGE_UCLOUD_LOCAL_SSD = "LOCAL_SSD" // SSD本地盘 STORAGE_UCLOUD_EXCLUSIVE_LOCAL_DISK = "EXCLUSIVE_LOCAL_DISK" // 独享本地盘 + + // Zstack storage type + STORAGE_ZSTACK_LOCAL_STORAGE = "localstorage" + STORAGE_ZSTACK_ROOT = "root" + STORAGE_ZSTACK_DATA = "data" + STORAGE_ZSTACK_IMAGECACHE = "imagecache" ) const ( @@ -94,6 +100,7 @@ var ( STORAGE_HUAWEI_SSD, STORAGE_HUAWEI_SAS, STORAGE_HUAWEI_SATA, STORAGE_OPENSTACK_ISCSI, STORAGE_UCLOUD_CLOUD_NORMAL, STORAGE_UCLOUD_CLOUD_SSD, STORAGE_UCLOUD_LOCAL_NORMAL, STORAGE_UCLOUD_LOCAL_SSD, STORAGE_UCLOUD_EXCLUSIVE_LOCAL_DISK, + STORAGE_ZSTACK_LOCAL_STORAGE, STORAGE_ZSTACK_ROOT, STORAGE_ZSTACK_DATA, STORAGE_ZSTACK_IMAGECACHE, } STORAGE_LIMITED_TYPES = []string{STORAGE_LOCAL, STORAGE_BAREMETAL, STORAGE_NAS, STORAGE_RBD, STORAGE_NFS} diff --git a/pkg/compute/guestdrivers/zstack.go b/pkg/compute/guestdrivers/zstack.go new file mode 100644 index 0000000000..01d5c915cd --- /dev/null +++ b/pkg/compute/guestdrivers/zstack.go @@ -0,0 +1,132 @@ +// 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 guestdrivers + +import ( + "context" + "fmt" + + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "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/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/billing" +) + +type SZStackGuestDriver struct { + SManagedVirtualizedGuestDriver +} + +func init() { + driver := SZStackGuestDriver{} + models.RegisterGuestDriver(&driver) +} + +func (self *SZStackGuestDriver) GetHypervisor() string { + return api.HYPERVISOR_ZSTACK +} + +func (self *SZStackGuestDriver) GetDefaultSysDiskBackend() string { + return api.STORAGE_ZSTACK_LOCAL_STORAGE +} + +func (self *SZStackGuestDriver) GetMinimalSysDiskSizeGb() int { + return 10 +} + +func (self *SZStackGuestDriver) GetStorageTypes() []string { + return []string{ + api.STORAGE_ZSTACK_LOCAL_STORAGE, + api.STORAGE_ZSTACK_DATA, + api.STORAGE_ZSTACK_ROOT, + } +} + +func (self *SZStackGuestDriver) ChooseHostStorage(host *models.SHost, backend string, storageIds []string) *models.SStorage { + return self.chooseHostStorage(self, host, backend, storageIds) +} + +func (self *SZStackGuestDriver) GetDetachDiskStatus() ([]string, error) { + return []string{api.VM_READY, api.VM_RUNNING}, nil +} + +func (self *SZStackGuestDriver) GetAttachDiskStatus() ([]string, error) { + return []string{api.VM_READY, api.VM_RUNNING}, nil +} + +func (self *SZStackGuestDriver) GetRebuildRootStatus() ([]string, error) { + return []string{api.VM_READY}, nil +} + +func (self *SZStackGuestDriver) GetChangeConfigStatus() ([]string, error) { + return []string{api.VM_READY, api.VM_RUNNING}, nil +} + +func (self *SZStackGuestDriver) GetDeployStatus() ([]string, error) { + return []string{api.VM_RUNNING}, nil +} + +func (self *SZStackGuestDriver) ValidateResizeDisk(guest *models.SGuest, disk *models.SDisk, storage *models.SStorage) error { + if !utils.IsInStringArray(guest.Status, []string{api.VM_READY, api.VM_RUNNING}) { + return fmt.Errorf("Cannot resize disk when guest in status %s", guest.Status) + } + if !utils.IsInStringArray(storage.StorageType, []string{api.STORAGE_ZSTACK_LOCAL_STORAGE, api.STORAGE_ZSTACK_ROOT, api.STORAGE_ZSTACK_DATA}) { + return fmt.Errorf("Cannot resize %s disk", storage.StorageType) + } + return nil +} + +func (self *SZStackGuestDriver) RequestDetachDisk(ctx context.Context, guest *models.SGuest, task taskman.ITask) error { + return guest.StartSyncTask(ctx, task.GetUserCred(), false, task.GetTaskId()) +} + +func (self *SZStackGuestDriver) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, input *api.ServerCreateInput) (*api.ServerCreateInput, error) { + input, err := self.SManagedVirtualizedGuestDriver.ValidateCreateData(ctx, userCred, input) + if err != nil { + return nil, err + } + if len(input.Networks) > 2 { + return nil, httperrors.NewInputParameterError("cannot support more than 1 nic") + } + return input, nil +} + +func (self *SZStackGuestDriver) GetGuestInitialStateAfterCreate() string { + return api.VM_RUNNING +} + +func (self *SZStackGuestDriver) GetGuestInitialStateAfterRebuild() string { + return api.VM_READY +} + +func (self *SZStackGuestDriver) GetLinuxDefaultAccount(desc cloudprovider.SManagedVMCreateConfig) string { + userName := "root" + if desc.ImageType == "system" && desc.OsType == "Windows" { + userName = "Administrator" + } + return userName +} + +func (self *SZStackGuestDriver) AllowReconfigGuest() bool { + return true +} + +func (self *SZStackGuestDriver) IsSupportedBillingCycle(bc billing.SBillingCycle) bool { + return false +} diff --git a/pkg/compute/hostdrivers/zstack.go b/pkg/compute/hostdrivers/zstack.go new file mode 100644 index 0000000000..f87e635e5d --- /dev/null +++ b/pkg/compute/hostdrivers/zstack.go @@ -0,0 +1,48 @@ +// 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 hostdrivers + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/httperrors" +) + +type SZStackHostDriver struct { + SManagedVirtualizationHostDriver +} + +func init() { + driver := SZStackHostDriver{} + models.RegisterHostDriver(&driver) +} + +func (self *SZStackHostDriver) GetHostType() string { + return api.HOST_TYPE_ZSTACK +} + +func (self *SZStackHostDriver) ValidateAttachStorage(host *models.SHost, storage *models.SStorage, data *jsonutils.JSONDict) error { + return httperrors.NewUnsupportOperationError("Not support attach storage for %s host", self.GetHostType()) +} + +func (self *SZStackHostDriver) ValidateDiskSize(storage *models.SStorage, sizeGb int) error { + if utils.IsInStringArray(storage.StorageType, []string{api.STORAGE_ZSTACK_IMAGECACHE, api.STORAGE_ZSTACK_ROOT}) { + return httperrors.NewUnsupportOperationError("Not support create %s disk", storage.StorageType) + } + return nil +} diff --git a/pkg/util/zstack/disk.go b/pkg/util/zstack/disk.go index f113f1b8fd..15722f71b6 100644 --- a/pkg/util/zstack/disk.go +++ b/pkg/util/zstack/disk.go @@ -3,6 +3,7 @@ package zstack import ( "context" "fmt" + "strings" "time" "yunion.io/x/jsonutils" @@ -13,7 +14,9 @@ import ( ) type SDisk struct { - storage *SStorage + localStorage *SLocalStorage + cephStorage *SCephStorage + region *SRegion ZStackBasic PrimaryStorageUUID string `json:"primaryStorageUuid"` @@ -32,23 +35,8 @@ type SDisk struct { ZStackTime } -func (region *SRegion) GetDiskWithStorage(diskId string) (*SDisk, error) { - disk, err := region.GetDisk(diskId) - if err != nil { - log.Errorf("failed to found disk %s error: %v", diskId, err) - return nil, err - } - storage, err := region.GetStorageWithZone(disk.PrimaryStorageUUID) - if err != nil { - log.Errorf("failed to found storage %s for disk %s error: %v", disk.PrimaryStorageUUID, disk.Name, err) - return nil, err - } - disk.storage = storage - return disk, nil -} - func (region *SRegion) GetDisk(diskId string) (*SDisk, error) { - disks, err := region.GetDisks("", diskId) + disks, err := region.GetDisks("", []string{diskId}, "") if err != nil { return nil, err } @@ -58,26 +46,79 @@ func (region *SRegion) GetDisk(diskId string) (*SDisk, error) { } return nil, cloudprovider.ErrNotFound } - if len(disks) == 0 { + if len(disks) == 0 || len(diskId) == 0 { return nil, cloudprovider.ErrNotFound } return nil, cloudprovider.ErrDuplicateId } -func (region *SRegion) GetDisks(storageId, diskId string) ([]SDisk, error) { +func (region *SRegion) GetDiskWithStorage(diskId string) (*SDisk, error) { + disk, err := region.GetDisk(diskId) + if err != nil { + return nil, err + } + disk.region = region + storage, err := region.GetPrimaryStorage(disk.PrimaryStorageUUID) + if err != nil { + return nil, err + } + switch storage.Type { + case StorageTypeLocal: + tags, err := region.GetSysTags("", "VolumeVO", disk.UUID, "") + if err != nil { + return nil, err + } + for i := 0; i < len(tags); i++ { + if strings.HasPrefix(tags[i].Tag, "localStorage::hostUuid::") { + hostInfo := strings.Split(tags[i].Tag, "localStorage::hostUuid::") + if len(hostInfo) == 2 { + localStorage, err := region.GetLocalStorage(storage.UUID, hostInfo[1]) + if err != nil { + return nil, err + } + disk.localStorage = localStorage + return disk, nil + } + return nil, fmt.Errorf("invalid host info %s from disk %s", tags[i].Tag, disk.Name) + } + } + return nil, cloudprovider.ErrNotFound + case StorageTypeCeph: + storage, err := region.GetPrimaryStorage(disk.PrimaryStorageUUID) + if err != nil { + return nil, err + } + for i := 0; i < len(storage.Pools); i++ { + if strings.Contains(disk.InstallPath, storage.Pools[i].PoolName) { + zone, err := region.GetZone(storage.ZoneUUID) + if err != nil { + return nil, err + } + cephStorage := storage.Pools[i] + cephStorage.zone = zone + disk.cephStorage = &cephStorage + return disk, nil + } + } + return nil, fmt.Errorf("failed to found ceph storage for disk %s", disk.Name) + default: + return nil, fmt.Errorf("Unsupport StorageType %s", storage.Type) + } +} + +func (region *SRegion) GetDisks(storageId string, diskIds []string, diskType string) ([]SDisk, error) { disks := []SDisk{} params := []string{} if len(storageId) > 0 { params = append(params, "q=primaryStorageUuid="+storageId) } - if len(diskId) > 0 { - params = append(params, "q=uuid="+diskId) + if len(diskIds) > 0 { + params = append(params, "q=uuid?="+strings.Join(diskIds, ",")) } - err := region.client.listAll("volumes", params, &disks) - if err != nil { - return nil, err + if len(diskType) > 0 { + params = append(params, "q=type="+diskType) } - return disks, nil + return disks, region.client.listAll("volumes", params, &disks) } func (disk *SDisk) GetMetadata() *jsonutils.JSONDict { @@ -92,11 +133,11 @@ func (disk *SDisk) GetId() string { } func (disk *SDisk) Delete(ctx context.Context) error { - return disk.storage.zone.region.DeleteDisk(disk.UUID) + return disk.region.DeleteDisk(disk.UUID) } func (disk *SDisk) Resize(ctx context.Context, sizeMb int64) error { - return disk.storage.zone.region.ResizeDisk(disk.UUID, disk.GetDiskType(), sizeMb) + return disk.region.ResizeDisk(disk.UUID, sizeMb) } func (disk *SDisk) GetName() string { @@ -112,7 +153,13 @@ func (disk *SDisk) IsEmulated() bool { } func (disk *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) { - return disk.storage, nil + if disk.localStorage != nil { + return disk.localStorage, nil + } + if disk.cephStorage != nil { + return disk.cephStorage, nil + } + return nil, cloudprovider.ErrNotFound } func (disk *SDisk) GetStatus() string { @@ -128,7 +175,7 @@ func (disk *SDisk) GetStatus() string { } func (disk *SDisk) Refresh() error { - new, err := disk.storage.zone.region.GetDisks("", disk.UUID) + new, err := disk.region.GetDisk(disk.UUID) if err != nil { return err } @@ -180,37 +227,69 @@ func (disk *SDisk) GetMountpoint() string { return "" } -func (region *SRegion) CreateDisk(zoneId string, category string, name string, sizeGb int, desc string) (string, error) { - return "", cloudprovider.ErrNotImplemented +func (region *SRegion) CreateDisk(name string, storageId string, hostId string, poolName string, sizeGb int, desc string) (*SDisk, error) { + offerings, err := region.GetDiskOfferings(sizeGb) + if err != nil { + return nil, err + } + diskOfferingUuid := "" + if len(offerings) > 0 { + diskOfferingUuid = offerings[0].UUID + } else { + offering, err := region.CreateDiskOffering(sizeGb) + if err != nil { + return nil, err + } + diskOfferingUuid = offering.UUID + defer region.DeleteDiskOffering(diskOfferingUuid) + } + params := map[string]interface{}{ + "params": map[string]string{ + "name": name, + "description": desc, + "diskOfferingUuid": diskOfferingUuid, + "primaryStorageUuid": storageId, + }, + } + if len(hostId) > 0 { + params["systemTags"] = []string{"localStorage::hostUuid::" + hostId} + } + if len(poolName) > 0 { + params["systemTags"] = []string{"ceph::pool::" + poolName} + } + resp, err := region.client.post("volumes/data", jsonutils.Marshal(params)) + disk := &SDisk{} + return disk, resp.Unmarshal(disk, "inventory") } func (region *SRegion) DeleteDisk(diskId string) error { - _, err := region.client.delete("volumes", diskId, "Enforcing") - return err + return region.client.delete("volumes", diskId, "Enforcing") } -func (region *SRegion) ResizeDisk(diskId string, diskType string, sizeMb int64) error { - switch diskType { - case api.DISK_TYPE_SYS: - diskType = "Root" - default: - diskType = "Data" +func (region *SRegion) ResizeDisk(diskId string, sizeMb int64) error { + disk, err := region.GetDisk(diskId) + if err != nil { + return err } params := jsonutils.Marshal(map[string]interface{}{ - fmt.Sprintf("resize%sVolume", diskType): map[string]int64{ + fmt.Sprintf("resize%sVolume", disk.Type): map[string]int64{ "size": sizeMb * 1024 * 1024, }, }) - _, err := region.client.put("volumes/resize", diskId, params) + resource := "volumes/resize" + if disk.Type == "Data" { + resource = "volumes/data/resize" + } + _, err = region.client.put(resource, diskId, params) return err } func (disk *SDisk) CreateISnapshot(ctx context.Context, name, desc string) (cloudprovider.ICloudSnapshot, error) { - return nil, cloudprovider.ErrNotImplemented + return disk.region.CreateSnapshot(name, disk.UUID, desc) } func (disk *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) { - snapshots, err := disk.storage.zone.region.GetSnapshots(snapshotId, disk.UUID) + snapshots, err := disk.region.GetSnapshots(snapshotId, disk.UUID) if err != nil { return nil, err } @@ -227,7 +306,7 @@ func (disk *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot } func (disk *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { - snapshots, err := disk.storage.zone.region.GetSnapshots("", disk.UUID) + snapshots, err := disk.region.GetSnapshots("", disk.UUID) if err != nil { return nil, err } @@ -239,7 +318,15 @@ func (disk *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { } func (disk *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) { - return "", cloudprovider.ErrNotImplemented + _, err := disk.region.ResetDisks(snapshotId) + return disk.UUID, err +} + +func (region *SRegion) ResetDisks(snapshotId string) (jsonutils.JSONObject, error) { + params := map[string]interface{}{ + "revertVolumeFromSnapshot": jsonutils.NewDict(), + } + return region.client.put("volume-snapshots", snapshotId, jsonutils.Marshal(params)) } func (disk *SDisk) GetBillingType() string { @@ -259,11 +346,15 @@ func (disk *SDisk) GetAccessPath() string { } func (disk *SDisk) Rebuild(ctx context.Context) error { - return disk.storage.zone.region.RebuildDisk(disk.UUID) + return disk.region.RebuildDisk(disk.UUID) } func (region *SRegion) RebuildDisk(diskId string) error { - return cloudprovider.ErrNotImplemented + params := map[string]interface{}{ + "recoverDataVolume": jsonutils.NewDict(), + } + _, err := region.client.put("volumes", diskId, jsonutils.Marshal(params)) + return err } func (disk *SDisk) GetProjectId() string { diff --git a/pkg/util/zstack/disk_offering.go b/pkg/util/zstack/disk_offering.go new file mode 100644 index 0000000000..34a2b9608f --- /dev/null +++ b/pkg/util/zstack/disk_offering.go @@ -0,0 +1,43 @@ +package zstack + +import ( + "fmt" + + "yunion.io/x/jsonutils" +) + +type SDiskOffering struct { + ZStackBasic + DiskSize int `json:"diskSize"` + Type string `json:"type"` + State string `json:"state"` + AllocatorStrategy string `json:"allocatorStrategy"` +} + +func (region *SRegion) GetDiskOfferings(diskSizeGB int) ([]SDiskOffering, error) { + offerings := []SDiskOffering{} + params := []string{} + if diskSizeGB != 0 { + params = append(params, "q=diskSize="+fmt.Sprintf("%d", diskSizeGB*1024*1024*1024)) + } + return offerings, region.client.listAll("disk-offerings", params, &offerings) +} + +func (region *SRegion) CreateDiskOffering(diskSizeGB int) (*SDiskOffering, error) { + params := map[string]interface{}{ + "params": map[string]interface{}{ + "name": fmt.Sprintf("temp-disk-offering-%dGB", diskSizeGB), + "diskSize": diskSizeGB * 1024 * 1024 * 1024, + }, + } + resp, err := region.client.post("disk-offerings", jsonutils.Marshal(params)) + if err != nil { + return nil, err + } + offer := &SDiskOffering{} + return offer, resp.Unmarshal(offer, "inventory") +} + +func (region *SRegion) DeleteDiskOffering(offerId string) error { + return region.client.delete("disk-offerings", offerId, "") +} diff --git a/pkg/util/zstack/eip.go b/pkg/util/zstack/eip.go index 0ab7b9d755..ec77c0eef9 100644 --- a/pkg/util/zstack/eip.go +++ b/pkg/util/zstack/eip.go @@ -32,7 +32,7 @@ func (region *SRegion) GetEip(eipId string) (*SEipAddress, error) { } return nil, cloudprovider.ErrNotFound } - if len(eips) == 0 { + if len(eips) == 0 || len(eipId) == 0 { return nil, cloudprovider.ErrNotFound } return nil, cloudprovider.ErrDuplicateId diff --git a/pkg/util/zstack/host.go b/pkg/util/zstack/host.go index 05230e382e..b23dd7fcbc 100644 --- a/pkg/util/zstack/host.go +++ b/pkg/util/zstack/host.go @@ -1,8 +1,6 @@ package zstack import ( - "fmt" - "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/cloudprovider" @@ -47,14 +45,27 @@ func (host *SHost) GetIWires() ([]cloudprovider.ICloudWire, error) { } func (host *SHost) GetIStorages() ([]cloudprovider.ICloudStorage, error) { - storages, err := host.zone.region.GetStorages(host.zone.UUID, host.ClusterUUID, "") + primaryStorages, err := host.zone.region.GetPrimaryStorages(host.zone.UUID, host.ClusterUUID, "") if err != nil { return nil, err } istorages := []cloudprovider.ICloudStorage{} - for i := 0; i < len(storages); i++ { - storages[i].zone = host.zone - istorages = append(istorages, &storages[i]) + for i := 0; i < len(primaryStorages); i++ { + switch primaryStorages[i].Type { + case StorageTypeLocal: + storages, err := host.zone.region.getILocalStorages(host.zone, primaryStorages[i].UUID, host.UUID) + if err != nil { + return nil, err + } + istorages = append(istorages, storages...) + case StorageTypeCeph: + storages, err := host.zone.region.getICephStorages(host.zone, primaryStorages[i].UUID) + if err != nil { + return nil, err + } + istorages = append(istorages, storages...) + case StorageTypeVCenter: + } } return istorages, nil } @@ -88,7 +99,7 @@ func (host *SHost) GetIVMById(instanceId string) (cloudprovider.ICloudVM, error) } return nil, cloudprovider.ErrNotFound } - if len(instances) == 0 { + if len(instances) == 0 || len(instanceId) == 0 { return nil, cloudprovider.ErrNotFound } return nil, cloudprovider.ErrDuplicateId @@ -111,8 +122,10 @@ func (host *SHost) IsEmulated() bool { } func (host *SHost) GetStatus() string { - //TODO - return api.HOST_STATUS_RUNNING + if host.Status == "Connected" { + return api.HOST_STATUS_RUNNING + } + return api.HOST_STATUS_UNKNOWN } func (host *SHost) Refresh() error { @@ -124,7 +137,7 @@ func (host *SHost) GetHostStatus() string { } func (host *SHost) GetEnabled() bool { - return true + return host.State == "Enabled" } func (host *SHost) GetAccessIp() string { @@ -165,37 +178,20 @@ func (host *SHost) GetMemSizeMB() int { return host.TotalMemoryCapacity / 1024 / 1024 } -type SLocalStorageCapacity struct { - HostUUID string `json:"hostUuid"` - TotalCapacity int `json:"totalCapacity"` - AvailableCapacity int `json:"availableCapacity"` - TotalPhysicalCapacity int `json:"totalPhysicalCapacity"` - AvailablePhysicalCapacity int `json:"availablePhysicalCapacity"` -} - -func (region *SRegion) GetLocalStorage(storageId string, hostId string) ([]SLocalStorageCapacity, error) { - localStorage := []SLocalStorageCapacity{} - params := []string{} - if len(hostId) > 0 { - params = append(params, "hostUuid="+hostId) - } - return localStorage, region.client.listAll(fmt.Sprintf("primary-storage/local-storage/%s/capacities", storageId), params, &localStorage) -} - func (host *SHost) GetStorageSizeMB() int { - storages, err := host.zone.region.GetStorages(host.zone.UUID, host.ClusterUUID, "") + storages, err := host.zone.region.GetPrimaryStorages(host.zone.UUID, host.ClusterUUID, "") if err != nil { return 0 } totalStorage := 0 for _, storage := range storages { - if storage.Type == "LocalStorage" { - localStorages, err := host.zone.region.GetLocalStorage(storage.UUID, host.UUID) + if storage.Type == StorageTypeLocal { + localStorages, err := host.zone.region.GetLocalStorages(storage.UUID, host.UUID) if err != nil { return 0 } for i := 0; i < len(localStorages); i++ { - totalStorage += localStorages[i].TotalCapacity + totalStorage += int(localStorages[i].TotalCapacity) } } } diff --git a/pkg/util/zstack/image.go b/pkg/util/zstack/image.go index c560d94d15..ee885ebef5 100644 --- a/pkg/util/zstack/image.go +++ b/pkg/util/zstack/image.go @@ -65,7 +65,6 @@ func (image *SImage) IsEmulated() bool { func (image *SImage) Delete(ctx context.Context) error { return cloudprovider.ErrNotImplemented - //return image.storageCache.region.DeleteImage(image.UUID) } func (image *SImage) GetGlobalId() string { @@ -153,7 +152,7 @@ func (region *SRegion) GetImage(imageId string) (*SImage, error) { } return nil, cloudprovider.ErrNotFound } - if len(images) == 0 { + if len(images) == 0 || len(imageId) == 0 { return nil, cloudprovider.ErrNotFound } return nil, cloudprovider.ErrDuplicateId diff --git a/pkg/util/zstack/instance.go b/pkg/util/zstack/instance.go index 9e3b46511d..36901dcad2 100644 --- a/pkg/util/zstack/instance.go +++ b/pkg/util/zstack/instance.go @@ -55,7 +55,7 @@ func (region *SRegion) GetInstance(instanceId string) (*SInstance, error) { } return nil, cloudprovider.ErrNotFound } - if len(instances) == 0 { + if len(instances) == 0 || len(instanceId) == 0 { return nil, cloudprovider.ErrNotFound } return nil, cloudprovider.ErrDuplicateId diff --git a/pkg/util/zstack/network.go b/pkg/util/zstack/network.go index cefaaedd09..6c3e06a640 100644 --- a/pkg/util/zstack/network.go +++ b/pkg/util/zstack/network.go @@ -48,7 +48,7 @@ func (region *SRegion) GetNetwork(zoneId, wireId, l3Id, networkId string) (*SNet } return nil, cloudprovider.ErrNotFound } - if len(networks) == 0 { + if len(networks) == 0 || len(networkId) == 0 { return nil, cloudprovider.ErrNotFound } return nil, cloudprovider.ErrDuplicateId @@ -65,7 +65,7 @@ func (region *SRegion) GetL3Network(zoneId string, wireId string, l3Id string) ( } return nil, cloudprovider.ErrNotFound } - if len(l3Networks) == 0 { + if len(l3Networks) == 0 || len(l3Id) == 0 { return nil, cloudprovider.ErrNotFound } return nil, cloudprovider.ErrDuplicateId diff --git a/pkg/util/zstack/offering.go b/pkg/util/zstack/offering.go index f54e5ffdba..141594114b 100644 --- a/pkg/util/zstack/offering.go +++ b/pkg/util/zstack/offering.go @@ -32,7 +32,7 @@ func (region *SRegion) GetInstanceOffering(offerId string) (*SInstanceOffering, } return nil, cloudprovider.ErrNotFound } - if len(offerings) == 0 { + if len(offerings) == 0 || len(offerId) == 0 { return nil, cloudprovider.ErrNotFound } return nil, cloudprovider.ErrDuplicateId diff --git a/pkg/util/zstack/region.go b/pkg/util/zstack/region.go index 15cf7ac383..61617fd1ea 100644 --- a/pkg/util/zstack/region.go +++ b/pkg/util/zstack/region.go @@ -68,7 +68,16 @@ func (region *SRegion) GetIHostById(id string) (cloudprovider.ICloudHost, error) } func (region *SRegion) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { - return region.GetStorageWithZone(id) + storages, err := region.getIStorages("") + if err != nil { + return nil, err + } + for i := 0; i < len(storages); i++ { + if storages[i].GetGlobalId() == id { + return storages[i], nil + } + } + return nil, cloudprovider.ErrNotFound } func (region *SRegion) GetIHosts() ([]cloudprovider.ICloudHost, error) { @@ -84,15 +93,7 @@ func (region *SRegion) GetIHosts() ([]cloudprovider.ICloudHost, error) { } func (region *SRegion) GetIStorages() ([]cloudprovider.ICloudStorage, error) { - storages, err := region.GetStorages("", "", "") - if err != nil { - return nil, err - } - istorages := []cloudprovider.ICloudStorage{} - for i := 0; i < len(storages); i++ { - istorages = append(istorages, &storages[i]) - } - return istorages, nil + return region.getIStorages("") } func (region *SRegion) GetIStoragecacheById(id string) (cloudprovider.ICloudStoragecache, error) { @@ -141,7 +142,7 @@ func (region *SRegion) GetZone(zoneId string) (*SZone, error) { } return nil, cloudprovider.ErrNotFound } - if len(zones) == 0 { + if len(zones) == 0 || len(zoneId) == 0 { return nil, cloudprovider.ErrNotFound } return nil, cloudprovider.ErrDuplicateId diff --git a/pkg/util/zstack/securitygroup.go b/pkg/util/zstack/securitygroup.go index ca0137642b..8decbb9921 100644 --- a/pkg/util/zstack/securitygroup.go +++ b/pkg/util/zstack/securitygroup.go @@ -48,7 +48,7 @@ func (region *SRegion) GetSecurityGroup(secgroupId string) (*SSecurityGroup, err } return nil, cloudprovider.ErrNotFound } - if len(secgroups) == 0 { + if len(secgroups) == 0 || len(secgroupId) == 0 { return nil, cloudprovider.ErrNotFound } return nil, cloudprovider.ErrDuplicateId diff --git a/pkg/util/zstack/shell/disk.go b/pkg/util/zstack/shell/disk.go index d5d1273b57..1bbf95d890 100644 --- a/pkg/util/zstack/shell/disk.go +++ b/pkg/util/zstack/shell/disk.go @@ -8,14 +8,51 @@ import ( func init() { type DiskListOptions struct { StorageId string - DiskId string + DiskIds []string + DiskType string } shellutils.R(&DiskListOptions{}, "disk-list", "List disks", func(cli *zstack.SRegion, args *DiskListOptions) error { - disks, err := cli.GetDisks(args.StorageId, args.DiskId) + disks, err := cli.GetDisks(args.StorageId, args.DiskIds, args.DiskType) if err != nil { return err } printList(disks, len(disks), 0, 0, []string{}) return nil }) + + type DiskCreateOptions struct { + NAME string + Description string + SizeGB int + HostId string + PoolId string + STORAGE_ID string + } + + shellutils.R(&DiskCreateOptions{}, "disk-create", "Create disk", func(cli *zstack.SRegion, args *DiskCreateOptions) error { + disk, err := cli.CreateDisk(args.NAME, args.STORAGE_ID, args.HostId, args.PoolId, args.SizeGB, args.Description) + if err != nil { + return err + } + printObject(disk) + return nil + }) + + type DiskDelete struct { + ID string + } + + shellutils.R(&DiskDelete{}, "disk-delete", "Delete disk", func(cli *zstack.SRegion, args *DiskDelete) error { + return cli.DeleteDisk(args.ID) + }) + + type DiskResize struct { + ID string + SIZEGB int64 + } + + shellutils.R(&DiskResize{}, "disk-resize", "Resize disk", func(cli *zstack.SRegion, args *DiskResize) error { + return cli.ResizeDisk(args.ID, args.SIZEGB*1024) + }) + } diff --git a/pkg/util/zstack/shell/disk_offering.go b/pkg/util/zstack/shell/disk_offering.go new file mode 100644 index 0000000000..7e30ec8eb3 --- /dev/null +++ b/pkg/util/zstack/shell/disk_offering.go @@ -0,0 +1,42 @@ +package shell + +import ( + "yunion.io/x/onecloud/pkg/util/shellutils" + "yunion.io/x/onecloud/pkg/util/zstack" +) + +func init() { + type DiskOfferingOptions struct { + SizeGb int + } + shellutils.R(&DiskOfferingOptions{}, "disk-offering-list", "List disk offerings", func(cli *zstack.SRegion, args *DiskOfferingOptions) error { + offerings, err := cli.GetDiskOfferings(args.SizeGb) + if err != nil { + return err + } + printList(offerings, len(offerings), 0, 0, []string{}) + return nil + }) + + type DiskOfferingCreateOptions struct { + SIZE_GB int + } + + shellutils.R(&DiskOfferingCreateOptions{}, "disk-offering-create", "Create disk offering", func(cli *zstack.SRegion, args *DiskOfferingCreateOptions) error { + offering, err := cli.CreateDiskOffering(args.SIZE_GB) + if err != nil { + return err + } + printObject(offering) + return nil + }) + + type DiskOfferingDeleteOptions struct { + ID string + } + + shellutils.R(&DiskOfferingDeleteOptions{}, "disk-offering-delete", "Delete disk offering", func(cli *zstack.SRegion, args *DiskOfferingDeleteOptions) error { + return cli.DeleteDiskOffering(args.ID) + }) + +} diff --git a/pkg/util/zstack/shell/snapshot.go b/pkg/util/zstack/shell/snapshot.go index e41235d43b..6c3ebc4e9a 100644 --- a/pkg/util/zstack/shell/snapshot.go +++ b/pkg/util/zstack/shell/snapshot.go @@ -18,4 +18,27 @@ func init() { printList(snapshots, len(snapshots), 0, 0, []string{}) return nil }) + + type SnapshoDeleteOptions struct { + ID string + } + + shellutils.R(&SnapshoDeleteOptions{}, "snapshot-delete", "Delete snapshot", func(cli *zstack.SRegion, args *SnapshoDeleteOptions) error { + return cli.DeleteSnapshot(args.ID) + }) + + type SnapshoCreateOptions struct { + DISKID string + NAME string + Desc string + } + + shellutils.R(&SnapshoCreateOptions{}, "snapshot-create", "Create snapshot", func(cli *zstack.SRegion, args *SnapshoCreateOptions) error { + snapshot, err := cli.CreateSnapshot(args.NAME, args.DISKID, args.Desc) + if err != nil { + return err + } + printObject(snapshot) + return nil + }) } diff --git a/pkg/util/zstack/shell/storage.go b/pkg/util/zstack/shell/storage.go index d79a5024a7..c3d3867dee 100644 --- a/pkg/util/zstack/shell/storage.go +++ b/pkg/util/zstack/shell/storage.go @@ -11,8 +11,8 @@ func init() { ClusterId string Id string } - shellutils.R(&StorageListOptions{}, "storage-list", "List storages", func(cli *zstack.SRegion, args *StorageListOptions) error { - storages, err := cli.GetStorages(args.ZoneId, args.ClusterId, args.Id) + shellutils.R(&StorageListOptions{}, "primary-storage-list", "List storages", func(cli *zstack.SRegion, args *StorageListOptions) error { + storages, err := cli.GetPrimaryStorages(args.ZoneId, args.ClusterId, args.Id) if err != nil { return err } @@ -25,8 +25,8 @@ func init() { HostId string } - shellutils.R(&LocalStorageOptions{}, "local-storage-show", "Show local storages", func(cli *zstack.SRegion, args *LocalStorageOptions) error { - storages, err := cli.GetLocalStorage(args.STORAGE_ID, args.HostId) + shellutils.R(&LocalStorageOptions{}, "local-storage-list", "Show local storages", func(cli *zstack.SRegion, args *LocalStorageOptions) error { + storages, err := cli.GetLocalStorages(args.STORAGE_ID, args.HostId) if err != nil { return err } diff --git a/pkg/util/zstack/shell/tag.go b/pkg/util/zstack/shell/tag.go new file mode 100644 index 0000000000..c1248de53d --- /dev/null +++ b/pkg/util/zstack/shell/tag.go @@ -0,0 +1,23 @@ +package shell + +import ( + "yunion.io/x/onecloud/pkg/util/shellutils" + "yunion.io/x/onecloud/pkg/util/zstack" +) + +func init() { + type TagListOptions struct { + TagId string + ResourceType string + ResourceId string + Tag string + } + shellutils.R(&TagListOptions{}, "system-tag-list", "List system tags", func(cli *zstack.SRegion, args *TagListOptions) error { + tags, err := cli.GetSysTags(args.TagId, args.ResourceType, args.ResourceId, args.Tag) + if err != nil { + return err + } + printList(tags, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/util/zstack/snapshot.go b/pkg/util/zstack/snapshot.go index 9fc40b72c5..2a94821c59 100644 --- a/pkg/util/zstack/snapshot.go +++ b/pkg/util/zstack/snapshot.go @@ -1,6 +1,8 @@ package zstack import ( + "fmt" + "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" @@ -84,7 +86,7 @@ func (region *SRegion) GetSnapshot(snapshotId string) (*SSnapshot, error) { } return nil, cloudprovider.ErrNotFound } - if len(snapshots) == 0 { + if len(snapshots) == 0 || len(snapshotId) == 0 { return nil, cloudprovider.ErrNotFound } return nil, cloudprovider.ErrDuplicateId @@ -117,10 +119,25 @@ func (snapshot *SSnapshot) GetMetadata() *jsonutils.JSONDict { } func (region *SRegion) DeleteSnapshot(snapshotId string) error { - _, err := region.client.delete("volume-snapshots", snapshotId, "Enforcing") - return err + return region.client.delete("volume-snapshots", snapshotId, "Enforcing") } func (snapshot *SSnapshot) GetProjectId() string { return "" } + +func (region *SRegion) CreateSnapshot(name, diskId, desc string) (*SSnapshot, error) { + params := map[string]interface{}{ + "params": map[string]string{ + "name": name, + "description": desc, + }, + } + resource := fmt.Sprintf("volumes/%s/volume-snapshots", diskId) + resp, err := region.client.post(resource, jsonutils.Marshal(params)) + if err != nil { + return nil, err + } + snapshot := &SSnapshot{region: region} + return snapshot, resp.Unmarshal(snapshot, "inventory") +} diff --git a/pkg/util/zstack/storage.go b/pkg/util/zstack/storage.go index a27900dce5..b1ccc9f4f6 100644 --- a/pkg/util/zstack/storage.go +++ b/pkg/util/zstack/storage.go @@ -1,168 +1,35 @@ package zstack import ( - "yunion.io/x/jsonutils" - "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" - - api "yunion.io/x/onecloud/pkg/apis/compute" ) -type TStorageType string - -const ( - StorageTypeCeph = TStorageType("Ceph") - StorageTypeLocal = TStorageType("LocalStorage") - StorageTypeVCenter = TStorageType("VCenter") -) - -type SStorage struct { - zone *SZone - - ZStackBasic - VCenterUUID string `json:"VCenterUuid"` - Datastore string `json:"datastore"` - ZoneUUID string `json:"zoneUuid"` - URL string `json:"url"` - TotalCapacity int64 `json:"totalCapacity"` - AvailableCapacity int `json:"availableCapacity"` - TotalPhysicalCapacity int `json:"totalPhysicalCapacity"` - AvailablePhysicalCapacity int `json:"availablePhysicalCapacity"` - Type TStorageType `json:"type"` - State string `json:"state"` - Status string `json:"status"` - MountPath string `json:"mountPath"` - AttachedClusterUUIDs []string `json:"attachedClusterUuids"` - ZStackTime -} - -func (storage *SStorage) GetMetadata() *jsonutils.JSONDict { - return nil -} - -func (storage *SStorage) GetId() string { - return storage.UUID -} - -func (storage *SStorage) GetName() string { - return storage.Name -} - -func (storage *SStorage) GetGlobalId() string { - return storage.GetId() -} - -func (storage *SStorage) IsEmulated() bool { - return false -} - -func (storage *SStorage) GetIZone() cloudprovider.ICloudZone { - return storage.zone -} - -func (storage *SStorage) GetIDisks() ([]cloudprovider.ICloudDisk, error) { - disks, err := storage.zone.region.GetDisks(storage.UUID, "") +func (region *SRegion) getIStorages(zondId string) ([]cloudprovider.ICloudStorage, error) { + primaryStorages, err := region.GetPrimaryStorages(zondId, "", "") if err != nil { return nil, err } - idisks := []cloudprovider.ICloudDisk{} - for i := 0; i < len(disks); i++ { - disks[i].storage = storage - idisks = append(idisks, &disks[i]) - } - return idisks, nil -} - -func (storage *SStorage) GetStorageType() string { - return string(storage.Type) -} - -func (storage *SStorage) GetMediumType() string { - return api.DISK_TYPE_ROTATE -} - -func (storage *SStorage) GetCapacityMB() int64 { - return storage.TotalCapacity / 1024 / 1024 -} - -func (storage *SStorage) GetStorageConf() jsonutils.JSONObject { - conf := jsonutils.NewDict() - return conf -} - -func (storage *SStorage) GetManagerId() string { - return storage.zone.region.client.providerID -} - -func (storage *SStorage) GetStatus() string { - if storage.Status == "Connected" { - return api.STORAGE_ONLINE - } - return api.STORAGE_OFFLINE -} - -func (storage *SStorage) Refresh() error { - // do nothing - return nil -} - -func (storage *SStorage) GetEnabled() bool { - return storage.State == "Enabled" -} - -func (storage *SStorage) GetIStoragecache() cloudprovider.ICloudStoragecache { - storage.zone.region.GetIStoragecaches() - return storage.zone.region.storageCache -} - -func (storage *SStorage) CreateIDisk(name string, sizeGb int, desc string) (cloudprovider.ICloudDisk, error) { - // diskId, err := storage.zone.region.CreateDisk(storage.zone.ZoneId, storage.storageType, name, sizeGb, desc) - // if err != nil { - // log.Errorf("createDisk fail %s", err) - // return nil, err - // } - // disk, err := storage.zone.region.getDisk(diskId) - // if err != nil { - // log.Errorf("getDisk fail %s", err) - // return nil, err - // } - // disk.storage = storage - // return disk, nil - return nil, cloudprovider.ErrNotImplemented -} - -func (storage *SStorage) GetIDiskById(diskId string) (cloudprovider.ICloudDisk, error) { - disks, err := storage.zone.region.GetDisks(storage.UUID, diskId) - if err != nil { - return nil, err - } - if len(disks) == 1 { - if disks[0].UUID == diskId { - disks[0].storage = storage - return &disks[0], nil + istorage := []cloudprovider.ICloudStorage{} + for _, primaryStorage := range primaryStorages { + zone, err := region.GetZone(primaryStorage.ZoneUUID) + if err != nil { + return nil, err + } + switch primaryStorage.Type { + case StorageTypeLocal: + ilocalStorages, err := region.getILocalStorages(zone, primaryStorage.UUID, "") + if err != nil { + return nil, err + } + istorage = append(istorage, ilocalStorages...) + case StorageTypeCeph: + icephStorage, err := region.getICephStorages(zone, primaryStorage.UUID) + if err != nil { + return nil, err + } + istorage = append(istorage, icephStorage...) + case StorageTypeVCenter: } - return nil, cloudprovider.ErrNotFound } - if len(disks) == 0 { - return nil, cloudprovider.ErrNotFound - } - return nil, cloudprovider.ErrDuplicateId -} - -func (storage *SStorage) GetMountPoint() string { - switch storage.Type { - case StorageTypeLocal: - return storage.MountPath - case StorageTypeCeph: - return "" - case StorageTypeVCenter: - return storage.URL - default: - log.Errorf("Unknown storage type %s", storage.Type) - } - return "" -} - -func (storage *SStorage) IsSysDiskStore() bool { - return false + return istorage, nil } diff --git a/pkg/util/zstack/storage_ceph.go b/pkg/util/zstack/storage_ceph.go new file mode 100644 index 0000000000..d499445b83 --- /dev/null +++ b/pkg/util/zstack/storage_ceph.go @@ -0,0 +1,172 @@ +package zstack + +import ( + "fmt" + "strings" + + "yunion.io/x/jsonutils" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type TCephPoolType string + +const ( + CephPoolTypeData = TCephPoolType("Data") + CephPoolTypeRoot = TCephPoolType("Root") + CephPoolTypeImageCache = TCephPoolType("ImageCache") +) + +type SCephStorage struct { + zone *SZone + + UUID string `json:"uuid"` + PrimaryStorageUUID string `json:"primaryStorageUuid"` + PoolName string `json:"poolName"` + Type TCephPoolType `json:"type"` + AvailableCapacity int64 `json:"availableCapacity"` + UsedCapacity int64 `json:"usedCapacity"` + ReplicatedSize int64 `json:"replicatedSize"` + TotalCapacity int64 `json:"totalCapacity"` + ZStackTime +} + +func (region *SRegion) getICephStorages(zone *SZone, storageId string) ([]cloudprovider.ICloudStorage, error) { + storage, err := region.GetPrimaryStorage(storageId) + if err != nil { + return nil, err + } + istorages := []cloudprovider.ICloudStorage{} + for i := 0; i < len(storage.Pools); i++ { + storage.Pools[i].zone = zone + istorages = append(istorages, &storage.Pools[i]) + } + return istorages, nil +} + +func (storage *SCephStorage) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (storage *SCephStorage) GetId() string { + return fmt.Sprintf("%s/%s", storage.PrimaryStorageUUID, storage.UUID) +} + +func (storage *SCephStorage) GetName() string { + primaryStorage, err := storage.zone.region.GetPrimaryStorage(storage.PrimaryStorageUUID) + if err != nil { + return "Unknown" + } + return fmt.Sprintf("%s/%s", primaryStorage.Name, storage.PoolName) +} + +func (storage *SCephStorage) GetGlobalId() string { + return storage.GetId() +} + +func (storage *SCephStorage) IsEmulated() bool { + return false +} + +func (storage *SCephStorage) GetIZone() cloudprovider.ICloudZone { + return storage.zone +} + +func (storage *SCephStorage) GetIDisks() ([]cloudprovider.ICloudDisk, error) { + if storage.Type == CephPoolTypeImageCache { + return []cloudprovider.ICloudDisk{}, nil + } + disks, err := storage.zone.region.GetDisks(storage.PrimaryStorageUUID, []string{}, string(storage.Type)) + if err != nil { + return nil, err + } + idisks := []cloudprovider.ICloudDisk{} + for i := 0; i < len(disks); i++ { + disks[i].cephStorage = storage + disks[i].region = storage.zone.region + idisks = append(idisks, &disks[i]) + } + return idisks, nil +} + +func (storage *SCephStorage) GetStorageType() string { + return strings.ToLower(string(storage.Type)) +} + +func (storage *SCephStorage) GetMediumType() string { + return api.DISK_TYPE_ROTATE +} + +func (storage *SCephStorage) GetCapacityMB() int64 { + return storage.TotalCapacity / 1024 / 1024 +} + +func (storage *SCephStorage) GetStorageConf() jsonutils.JSONObject { + conf := jsonutils.NewDict() + return conf +} + +func (storage *SCephStorage) GetManagerId() string { + return storage.zone.region.client.providerID +} + +func (storage *SCephStorage) GetStatus() string { + primaryStorage, err := storage.zone.region.GetPrimaryStorage(storage.PrimaryStorageUUID) + if err != nil { + return api.STORAGE_OFFLINE + } + return primaryStorage.GetStatus() +} + +func (storage *SCephStorage) Refresh() error { + // do nothing + return nil +} + +func (storage *SCephStorage) GetEnabled() bool { + return true +} + +func (storage *SCephStorage) GetIStoragecache() cloudprovider.ICloudStoragecache { + storage.zone.region.GetIStoragecaches() + return storage.zone.region.storageCache +} + +func (storage *SCephStorage) CreateIDisk(name string, sizeGb int, desc string) (cloudprovider.ICloudDisk, error) { + if storage.Type == CephPoolTypeData { + disk, err := storage.zone.region.CreateDisk(name, storage.PrimaryStorageUUID, "", storage.PoolName, sizeGb, desc) + if err != nil { + return nil, err + } + disk.cephStorage = storage + return disk, nil + } + return nil, cloudprovider.ErrNotSupported +} + +func (storage *SCephStorage) GetIDiskById(diskId string) (cloudprovider.ICloudDisk, error) { + disks, err := storage.zone.region.GetDisks(storage.PrimaryStorageUUID, []string{diskId}, storage.PoolName) + if err != nil { + return nil, err + } + if len(disks) == 1 { + if disks[0].UUID == diskId { + disks[0].region = storage.zone.region + disks[0].cephStorage = storage + return &disks[0], nil + } + return nil, cloudprovider.ErrNotFound + } + if len(disks) == 0 || len(diskId) == 0 { + return nil, cloudprovider.ErrNotFound + } + return nil, cloudprovider.ErrDuplicateId +} + +func (storage *SCephStorage) GetMountPoint() string { + return "ceph://" + storage.PoolName +} + +func (storage *SCephStorage) IsSysDiskStore() bool { + return storage.Type == CephPoolTypeRoot +} diff --git a/pkg/util/zstack/storage_local.go b/pkg/util/zstack/storage_local.go new file mode 100644 index 0000000000..42300865e8 --- /dev/null +++ b/pkg/util/zstack/storage_local.go @@ -0,0 +1,201 @@ +package zstack + +import ( + "fmt" + "strings" + + "yunion.io/x/jsonutils" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SLocalStorage struct { + zone *SZone + + primaryStorageID string + HostUUID string `json:"hostUuid"` + TotalCapacity int64 `json:"totalCapacity"` + AvailableCapacity int64 `json:"availableCapacity"` + TotalPhysicalCapacity int64 `json:"totalPhysicalCapacity"` + AvailablePhysicalCapacity int64 `json:"availablePhysicalCapacity"` +} + +func (region *SRegion) GetLocalStorage(storageId string, hostId string) (*SLocalStorage, error) { + storages, err := region.GetLocalStorages(storageId, hostId) + if err != nil { + return nil, err + } + if len(storages) == 1 { + if storages[0].HostUUID == hostId { + return &storages[0], nil + } + return nil, cloudprovider.ErrNotFound + } + if len(storages) == 0 || len(storageId) == 0 { + return nil, cloudprovider.ErrNotFound + } + return nil, cloudprovider.ErrDuplicateId +} + +func (region *SRegion) GetLocalStorages(storageId string, hostId string) ([]SLocalStorage, error) { + localStorage := []SLocalStorage{} + params := []string{} + if len(hostId) > 0 { + params = append(params, "hostUuid="+hostId) + } + err := region.client.listAll(fmt.Sprintf("primary-storage/local-storage/%s/capacities", storageId), params, &localStorage) + if err != nil { + return nil, err + } + for i := 0; i < len(localStorage); i++ { + localStorage[i].primaryStorageID = storageId + } + return localStorage, nil +} + +func (region *SRegion) getILocalStorages(zone *SZone, storageId, hostId string) ([]cloudprovider.ICloudStorage, error) { + storages, err := region.GetLocalStorages(storageId, hostId) + if err != nil { + return nil, err + } + istorage := []cloudprovider.ICloudStorage{} + for i := 0; i < len(storages); i++ { + storages[i].zone = zone + istorage = append(istorage, &storages[i]) + } + return istorage, nil +} + +func (storage *SLocalStorage) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (storage *SLocalStorage) GetId() string { + return fmt.Sprintf("%s/%s", storage.primaryStorageID, storage.HostUUID) +} + +func (storage *SLocalStorage) GetName() string { + primaryStorage, err := storage.zone.region.GetPrimaryStorage(storage.primaryStorageID) + if err != nil { + return "Unknown" + } + host, err := storage.zone.region.GetHost(storage.zone.UUID, storage.HostUUID) + if err != nil { + return "Unknown" + } + return fmt.Sprintf("%s/%s", primaryStorage.Name, host.Name) +} + +func (storage *SLocalStorage) GetGlobalId() string { + return storage.GetId() +} + +func (storage *SLocalStorage) IsEmulated() bool { + return false +} + +func (storage *SLocalStorage) GetIZone() cloudprovider.ICloudZone { + return storage.zone +} + +func (storage *SLocalStorage) GetIDisks() ([]cloudprovider.ICloudDisk, error) { + tags, err := storage.zone.region.GetSysTags("", "VolumeVO", "", "localStorage::hostUuid::"+storage.HostUUID) + if err != nil { + return nil, err + } + diskIds := []string{} + for i := 0; i < len(tags); i++ { + diskIds = append(diskIds, tags[i].ResourceUUID) + } + disks, err := storage.zone.region.GetDisks(storage.primaryStorageID, diskIds, "") + if err != nil { + return nil, err + } + idisks := []cloudprovider.ICloudDisk{} + for i := 0; i < len(disks); i++ { + disks[i].localStorage = storage + disks[i].region = storage.zone.region + idisks = append(idisks, &disks[i]) + } + return idisks, nil +} + +func (storage *SLocalStorage) GetStorageType() string { + return strings.ToLower(string(StorageTypeLocal)) +} + +func (storage *SLocalStorage) GetMediumType() string { + return api.DISK_TYPE_ROTATE +} + +func (storage *SLocalStorage) GetCapacityMB() int64 { + return storage.TotalCapacity / 1024 / 1024 +} + +func (storage *SLocalStorage) GetStorageConf() jsonutils.JSONObject { + conf := jsonutils.NewDict() + return conf +} + +func (storage *SLocalStorage) GetManagerId() string { + return storage.zone.region.client.providerID +} + +func (storage *SLocalStorage) GetStatus() string { + primaryStorage, err := storage.zone.region.GetPrimaryStorage(storage.primaryStorageID) + if err != nil { + return api.STORAGE_OFFLINE + } + return primaryStorage.GetStatus() +} + +func (storage *SLocalStorage) Refresh() error { + // do nothing + return nil +} + +func (storage *SLocalStorage) GetEnabled() bool { + return true +} + +func (storage *SLocalStorage) GetIStoragecache() cloudprovider.ICloudStoragecache { + storage.zone.region.GetIStoragecaches() + return storage.zone.region.storageCache +} + +func (storage *SLocalStorage) CreateIDisk(name string, sizeGb int, desc string) (cloudprovider.ICloudDisk, error) { + disk, err := storage.zone.region.CreateDisk(name, storage.primaryStorageID, storage.HostUUID, "", sizeGb, desc) + if err != nil { + return nil, err + } + disk.localStorage = storage + return disk, nil +} + +func (storage *SLocalStorage) GetIDiskById(diskId string) (cloudprovider.ICloudDisk, error) { + tags, err := storage.zone.region.GetSysTags("", "VolumeVO", diskId, "localStorage::hostUuid::"+storage.HostUUID) + if err != nil { + return nil, err + } + if len(tags) == 1 { + disk, err := storage.zone.region.GetDisk(diskId) + if err != nil { + return nil, err + } + disk.localStorage = storage + disk.region = storage.zone.region + return disk, nil + } + if len(tags) == 0 || len(diskId) == 0 { + return nil, cloudprovider.ErrNotFound + } + return nil, cloudprovider.ErrDuplicateId +} + +func (storage *SLocalStorage) GetMountPoint() string { + return "" +} + +func (storage *SLocalStorage) IsSysDiskStore() bool { + return false +} diff --git a/pkg/util/zstack/storage_primary.go b/pkg/util/zstack/storage_primary.go new file mode 100644 index 0000000000..15144f7ffa --- /dev/null +++ b/pkg/util/zstack/storage_primary.go @@ -0,0 +1,79 @@ +package zstack + +import ( + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type TStorageType string + +const ( + StorageTypeCeph = TStorageType("Ceph") + StorageTypeLocal = TStorageType("LocalStorage") + StorageTypeVCenter = TStorageType("VCenter") +) + +type SPrimaryStorage struct { + zone *SZone + + ZStackBasic + VCenterUUID string `json:"VCenterUuid"` + Datastore string `json:"datastore"` + ZoneUUID string `json:"zoneUuid"` + URL string `json:"url"` + TotalCapacity int64 `json:"totalCapacity"` + AvailableCapacity int `json:"availableCapacity"` + TotalPhysicalCapacity int `json:"totalPhysicalCapacity"` + AvailablePhysicalCapacity int `json:"availablePhysicalCapacity"` + Type TStorageType `json:"type"` + State string `json:"state"` + Status string `json:"status"` + MountPath string `json:"mountPath"` + AttachedClusterUUIDs []string `json:"attachedClusterUuids"` + + Pools []SCephStorage `json:"pools"` + + ZStackTime +} + +func (region *SRegion) GetPrimaryStorage(storageId string) (*SPrimaryStorage, error) { + storages, err := region.GetPrimaryStorages("", "", storageId) + if err != nil { + return nil, err + } + if len(storages) == 1 { + if storages[0].UUID == storageId { + return &storages[0], nil + } + return nil, cloudprovider.ErrNotFound + } + if len(storages) == 0 || len(storageId) == 0 { + return nil, cloudprovider.ErrNotFound + } + return nil, cloudprovider.ErrDuplicateId +} + +func (region *SRegion) GetPrimaryStorages(zoneId, clusterId, storageId string) ([]SPrimaryStorage, error) { + storages := []SPrimaryStorage{} + params := []string{} + if len(zoneId) > 0 { + params = append(params, "q=zone.uuid="+zoneId) + } + if len(clusterId) > 0 { + params = append(params, "q=cluster.uuid="+clusterId) + } + if SkipEsxi { + params = append(params, "q=type!=VCenter") + } + if len(storageId) > 0 { + params = append(params, "q=uuid="+storageId) + } + return storages, region.client.listAll("primary-storage", params, &storages) +} + +func (storage *SPrimaryStorage) GetStatus() string { + if storage.Status == "Connected" { + return api.STORAGE_ONLINE + } + return api.STORAGE_OFFLINE +} diff --git a/pkg/util/zstack/systag.go b/pkg/util/zstack/systag.go new file mode 100644 index 0000000000..e6565cb60c --- /dev/null +++ b/pkg/util/zstack/systag.go @@ -0,0 +1,29 @@ +package zstack + +type SSysTag struct { + ZStackTime + Inherent bool `json:"inherent"` + ResourceType string `json:"resourceType"` + ResourceUUID string `json:"resourceUuid"` + Tag string `json:"tag"` + Type string `json:"type"` + UUID string `json:"uuid"` +} + +func (region *SRegion) GetSysTags(tagId string, resourceType string, resourceId string, tag string) ([]SSysTag, error) { + tags := []SSysTag{} + params := []string{} + if len(tagId) > 0 { + params = append(params, "q=uuid="+tagId) + } + if len(resourceType) > 0 { + params = append(params, "q=resourceType="+resourceType) + } + if len(resourceId) > 0 { + params = append(params, "q=resourceUuid="+resourceId) + } + if len(tag) > 0 { + params = append(params, "q=tag="+tag) + } + return tags, region.client.listAll("system-tags", params, &tags) +} diff --git a/pkg/util/zstack/vpc.go b/pkg/util/zstack/vpc.go index 84e99f6094..0b74e206a6 100644 --- a/pkg/util/zstack/vpc.go +++ b/pkg/util/zstack/vpc.go @@ -24,7 +24,7 @@ func (vpc *SVpc) GetId() string { } func (vpc *SVpc) GetName() string { - return "ZStackVPC" + return DEFAULT_VPC_NAME } func (vpc *SVpc) GetGlobalId() string { diff --git a/pkg/util/zstack/zone.go b/pkg/util/zstack/zone.go index 234d09865b..9931ba9aac 100644 --- a/pkg/util/zstack/zone.go +++ b/pkg/util/zstack/zone.go @@ -2,7 +2,6 @@ package zstack import ( "yunion.io/x/jsonutils" - "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudprovider" api "yunion.io/x/onecloud/pkg/apis/compute" @@ -57,65 +56,12 @@ func (zone *SZone) GetIRegion() cloudprovider.ICloudRegion { return zone.region } -func (region *SRegion) GetStorageWithZone(storageId string) (*SStorage, error) { - storage, err := region.GetStorage(storageId) - if err != nil { - log.Errorf("failed to found storage %s error: %v", storageId, err) - return nil, err - } - zone, err := region.GetZone(storage.ZoneUUID) - if err != nil { - return nil, err - } - storage.zone = zone - return storage, nil -} - -func (region *SRegion) GetStorage(storageId string) (*SStorage, error) { - storages, err := region.GetStorages("", "", storageId) - if err != nil { - return nil, err - } - if len(storages) == 1 { - if storages[0].UUID == storageId { - return &storages[0], nil - } - return nil, cloudprovider.ErrNotFound - } - if len(storages) == 0 { - return nil, cloudprovider.ErrNotFound - } - return nil, cloudprovider.ErrDuplicateId -} - -func (region *SRegion) GetStorages(zoneId, clusterId, storageId string) ([]SStorage, error) { - storages := []SStorage{} - params := []string{} - if len(zoneId) > 0 { - params = append(params, "q=zone.uuid="+zoneId) - } - if len(clusterId) > 0 { - params = append(params, "q=cluster.uuid="+clusterId) - } - if SkipEsxi { - params = append(params, "q=type!=VCenter") - } - if len(storageId) > 0 { - params = append(params, "q=uuid="+storageId) - } - return storages, region.client.listAll("primary-storage", params, &storages) -} - func (zone *SZone) fetchStorages(clusterId string) error { - storages, err := zone.region.GetStorages(zone.UUID, clusterId, "") + storages, err := zone.region.getIStorages(zone.UUID) if err != nil { return err } - zone.istorages = []cloudprovider.ICloudStorage{} - for i := 0; i < len(storages); i++ { - storages[i].zone = zone - zone.istorages = append(zone.istorages, &storages[i]) - } + zone.istorages = storages return nil } @@ -127,21 +73,16 @@ func (zone *SZone) GetIStorages() ([]cloudprovider.ICloudStorage, error) { } func (zone *SZone) GetIStorageById(storageId string) (cloudprovider.ICloudStorage, error) { - storages, err := zone.region.GetStorages(zone.UUID, "", storageId) + err := zone.fetchStorages("") if err != nil { return nil, err } - if len(storages) == 1 { - if storages[0].UUID == storageId { - storages[0].zone = zone - return &storages[0], nil + for i := 0; i < len(zone.istorages); i++ { + if zone.istorages[i].GetGlobalId() == storageId { + return zone.istorages[i], nil } - return nil, cloudprovider.ErrNotFound } - if len(storages) == 0 { - return nil, cloudprovider.ErrNotFound - } - return nil, cloudprovider.ErrDuplicateId + return nil, cloudprovider.ErrNotFound } func (region *SRegion) GetHosts(zoneId string, hostId string) ([]SHost, error) { @@ -170,7 +111,7 @@ func (region *SRegion) GetHost(zoneId string, hostId string) (*SHost, error) { } return nil, cloudprovider.ErrNotFound } - if len(hosts) == 0 { + if len(hosts) == 0 || len(hostId) == 0 { return nil, cloudprovider.ErrNotFound } return nil, cloudprovider.ErrDuplicateId diff --git a/pkg/util/zstack/zstack.go b/pkg/util/zstack/zstack.go index 23e7afbd8c..89ba1bfcae 100644 --- a/pkg/util/zstack/zstack.go +++ b/pkg/util/zstack/zstack.go @@ -141,11 +141,16 @@ func (cli *SZStackClient) _list(resource string, start int, limit int, params [] } func (cli *SZStackClient) getDeleteURL(resource, resourceId, deleteMode string) string { - return cli.authURL + fmt.Sprintf("/zstack/%s/%s/%s?deleteMode=%s", ZSTACK_API_VERSION, resource, resourceId, deleteMode) + url := cli.authURL + fmt.Sprintf("/zstack/%s/%s/%s", ZSTACK_API_VERSION, resource, resourceId) + if len(deleteMode) > 0 { + url += "?deleteMode=" + deleteMode + } + return url } -func (cli *SZStackClient) delete(resource, resourceId, deleteMode string) (jsonutils.JSONObject, error) { - return cli._delete(resource, resourceId, deleteMode) +func (cli *SZStackClient) delete(resource, resourceId, deleteMode string) error { + _, err := cli._delete(resource, resourceId, deleteMode) + return err } func (cli *SZStackClient) _delete(resource, resourceId, deleteMode string) (jsonutils.JSONObject, error) { @@ -155,6 +160,13 @@ func (cli *SZStackClient) _delete(resource, resourceId, deleteMode string) (json header.Add("Authorization", "OAuth "+cli.sessionID) requestURL := cli.getDeleteURL(resource, resourceId, deleteMode) _, resp, err := httputils.JSONRequest(client, context.Background(), "DELETE", requestURL, header, nil, cli.debug) + if err != nil { + return nil, err + } + if resp.Contains("location") { + location, _ := resp.GetString("location") + return cli.wait(client, header, "delete", requestURL, jsonutils.NewDict(), location) + } return resp, err } @@ -177,7 +189,14 @@ func (cli *SZStackClient) _put(resource, resourceId string, params jsonutils.JSO header.Add("Authorization", "OAuth "+cli.sessionID) requestURL := cli.getURL(resource, resourceId, "actions") _, resp, err := httputils.JSONRequest(client, context.Background(), "PUT", requestURL, header, params, cli.debug) - return resp, err + if err != nil { + return nil, err + } + if resp.Contains("location") { + location, _ := resp.GetString("location") + return cli.wait(client, header, "update", requestURL, params, location) + } + return resp, nil } func (cli *SZStackClient) get(resource, resourceId string, spec string) (jsonutils.JSONObject, error) { @@ -198,6 +217,30 @@ func (cli *SZStackClient) post(resource string, params jsonutils.JSONObject) (js return cli._post(resource, params) } +func (cli *SZStackClient) wait(client *http.Client, header http.Header, action string, requestURL string, params jsonutils.JSONObject, location string) (jsonutils.JSONObject, error) { + startTime := time.Now() + timeout := time.Minute * 30 + for { + resp, err := httputils.Request(client, context.Background(), "GET", location, header, nil, cli.debug) + if err != nil { + return nil, err + } + _, result, err := httputils.ParseJSONResponse(resp, err, cli.debug) + if err != nil { + return nil, err + } + if time.Now().Sub(startTime) > timeout { + return nil, fmt.Errorf("timeout for waitting %s %s params: %s", action, requestURL, params.PrettyString()) + } + if resp.StatusCode != 200 { + log.Debugf("wait for job %s %s %s complete", action, requestURL, params.String()) + time.Sleep(5 * time.Second) + continue + } + return result, nil + } +} + func (cli *SZStackClient) _post(resource string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { client := httputils.GetDefaultClient() header := http.Header{} @@ -209,27 +252,10 @@ func (cli *SZStackClient) _post(resource string, params jsonutils.JSONObject) (j return nil, err } if resp.Contains("location") { - startTime := time.Now() - timeout := time.Minute * 30 location, _ := resp.GetString("location") - for { - _resp, err := httputils.Request(client, context.Background(), "GET", location, header, nil, cli.debug) - if err != nil { - return nil, err - } - _, resp, err = httputils.ParseJSONResponse(_resp, err, cli.debug) - if time.Now().Sub(startTime) > timeout { - return nil, fmt.Errorf("timeout for waitting post %s params: %s", requestURL, params.PrettyString()) - } - if _resp.StatusCode != 200 { - log.Debugf("wait for job %s %s complete", requestURL, params.String()) - time.Sleep(5 * time.Second) - continue - } - break - } + return cli.wait(client, header, "create", requestURL, params, location) } - return resp, err + return resp, nil } func (cli *SZStackClient) list(baseURL string, start int, limit int, params []string, retVal interface{}) error { diff --git a/pkg/util/zstack/zstack_const.go b/pkg/util/zstack/zstack_const.go index 27dbec2f53..9734b52e88 100644 --- a/pkg/util/zstack/zstack_const.go +++ b/pkg/util/zstack/zstack_const.go @@ -12,3 +12,7 @@ type ZStackBasic struct { Name string `json:"name"` Description string `json:"description"` } + +const ( + DEFAULT_VPC_NAME = "ZStackVPC" +) diff --git a/vendor/yunion.io/x/pkg/util/regutils/regutils.go b/vendor/yunion.io/x/pkg/util/regutils/regutils.go index 559bb44a6c..8597a56b5c 100644 --- a/vendor/yunion.io/x/pkg/util/regutils/regutils.go +++ b/vendor/yunion.io/x/pkg/util/regutils/regutils.go @@ -70,7 +70,7 @@ func init() { ISO_NO_SECOND_TIME_REG = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}Z$`) FULLISO_TIME_REG = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$`) COMPACT_TIME_REG = regexp.MustCompile(`^\d{14}$`) - ZSTACK_TIME_REG = regexp.MustCompile(`^\w.+\d{1,2}, \d{4} \d{1,2}:\d{1,2}:\d{1,2} (AM|PM)$`) //ZStack time format "Apr 1, 2019 3:23:17 PM" + ZSTACK_TIME_REG = regexp.MustCompile(`^\w+ \d{1,2}, \d{4} \d{1,2}:\d{1,2}:\d{1,2} (AM|PM)$`) //ZStack time format "Apr 1, 2019 3:23:17 PM" MYSQL_TIME_REG = regexp.MustCompile(`^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$`) NORMAL_TIME_REG = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$`) FULLNORMAL_TIME_REG = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}$`) diff --git a/vendor/yunion.io/x/pkg/util/timeutils/timeutils.go b/vendor/yunion.io/x/pkg/util/timeutils/timeutils.go index 614d0231af..2eba33ade5 100644 --- a/vendor/yunion.io/x/pkg/util/timeutils/timeutils.go +++ b/vendor/yunion.io/x/pkg/util/timeutils/timeutils.go @@ -133,7 +133,7 @@ func ParseShortDate(str string) (time.Time, error) { } func ParseZStackDate(str string) (time.Time, error) { - return time.Parse(ZStackTimeFormat, str) + return time.ParseInLocation(ZStackTimeFormat, str, time.Local) } func ParseTimeStr(str string) (time.Time, error) {