mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-21 14:19:49 +08:00
Merge pull request #18786 from swordqiu/automated-cherry-pick-of-#18784-upstream-master
Automated cherry pick of #18784: feature: backup support object storage
This commit is contained in:
@@ -26,6 +26,7 @@ func init() {
|
||||
bsCmd.List(&compute.BackupStorageListOptions{})
|
||||
bsCmd.Show(&compute.BackupStorageIdOptions{})
|
||||
bsCmd.Create(&compute.BackupStorageCreateOptions{})
|
||||
bsCmd.Update(&compute.BackupStorageUpdateOptions{})
|
||||
bsCmd.Delete(&compute.BackupStorageIdOptions{})
|
||||
bsCmd.Perform("public", &options.BasePublicOptions{})
|
||||
bsCmd.Perform("private", &options.BaseIdOptions{})
|
||||
|
||||
+41
-12
@@ -20,8 +20,12 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
)
|
||||
|
||||
type TBackupStorageType string
|
||||
|
||||
const (
|
||||
BACKUPSTORAGE_TYPE_NFS = "nfs"
|
||||
BACKUPSTORAGE_TYPE_NFS = TBackupStorageType("nfs")
|
||||
BACKUPSTORAGE_TYPE_OBJECT_STORAGE = TBackupStorageType("object")
|
||||
|
||||
BACKUPSTORAGE_STATUS_ONLINE = "online"
|
||||
BACKUPSTORAGE_STATUS_OFFLINE = "offline"
|
||||
|
||||
@@ -55,27 +59,26 @@ type BackupStorageCreateInput struct {
|
||||
// enum: nfs
|
||||
StorageType string `json:"storage_type"`
|
||||
|
||||
// description: host of nfs, storage_type 为 nfs 时, 此参数必传
|
||||
// example: 192.168.222.2
|
||||
NfsHost string `json:"nfs_host"`
|
||||
|
||||
// description: shared dir of nfs, storage_type 为 nfs 时, 此参数必传
|
||||
// example: /nfs_root/
|
||||
NfsSharedDir string `json:"nfs_shared_dir"`
|
||||
SBackupStorageAccessInfo
|
||||
|
||||
// description: Capacity size in MB
|
||||
CapacityMb int `json:"capacity_mb"`
|
||||
}
|
||||
|
||||
type BackupStorageAccessInfo struct {
|
||||
AccessUrl string
|
||||
type BackupStorageUpdateInput struct {
|
||||
apis.EnabledStatusInfrasResourceBaseUpdateInput
|
||||
|
||||
SBackupStorageAccessInfo
|
||||
}
|
||||
|
||||
/*type BackupStorageAccessInfo struct {
|
||||
AccessUrl string
|
||||
}*/
|
||||
|
||||
type BackupStorageDetails struct {
|
||||
apis.EnabledStatusInfrasResourceBaseDetails
|
||||
|
||||
NfsHost string
|
||||
NfsSharedDir string
|
||||
SBackupStorageAccessInfo
|
||||
}
|
||||
|
||||
type BackupStorageListInput struct {
|
||||
@@ -162,3 +165,29 @@ type InstanceBackupPackMetadata struct {
|
||||
|
||||
type InstanceBackupManagerSyncstatusInput struct {
|
||||
}
|
||||
|
||||
type SBackupStorageAccessInfo struct {
|
||||
// description: host of nfs, storage_type 为 nfs 时, 此参数必传
|
||||
// example: 192.168.222.2
|
||||
NfsHost string `json:"nfs_host"`
|
||||
|
||||
// description: shared dir of nfs, storage_type 为 nfs 时, 此参数必传
|
||||
// example: /nfs_root/
|
||||
NfsSharedDir string `json:"nfs_shared_dir"`
|
||||
|
||||
// description: access url of object storage bucket
|
||||
// example: https://qxxxxxo.tos-cn-beijing.volces.com
|
||||
ObjectBucketUrl string `json:"object_bucket_url"`
|
||||
// description: access key of object storage
|
||||
ObjectAccessKey string `json:"object_access_key"`
|
||||
// description: secret of object storage
|
||||
ObjectSecret string `json:"object_secret"`
|
||||
}
|
||||
|
||||
func (ba *SBackupStorageAccessInfo) String() string {
|
||||
return jsonutils.Marshal(ba).String()
|
||||
}
|
||||
|
||||
func (ba *SBackupStorageAccessInfo) IsZero() bool {
|
||||
return ba == nil
|
||||
}
|
||||
|
||||
@@ -16,10 +16,12 @@ package compute
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"yunion.io/x/cloudmux/pkg/apis/compute"
|
||||
"yunion.io/x/cloudmux/pkg/cloudprovider"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/gotypes"
|
||||
"yunion.io/x/pkg/util/regutils"
|
||||
"yunion.io/x/pkg/utils"
|
||||
|
||||
@@ -360,3 +362,9 @@ type BucketRefererConf struct {
|
||||
func (input *BucketRefererConf) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
gotypes.RegisterSerializable(reflect.TypeOf(&SBackupStorageAccessInfo{}), func() gotypes.ISerializable {
|
||||
return &SBackupStorageAccessInfo{}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
// 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 compute
|
||||
|
||||
import (
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
)
|
||||
|
||||
type DnsTrafficPolicyCreateInput struct {
|
||||
apis.EnabledStatusInfrasResourceBaseCreateInput
|
||||
|
||||
Provider string `json:"provider"`
|
||||
PolicyType string `json:"policy_type"`
|
||||
PolicyValue string `json:"policy_value"`
|
||||
// 额外参数
|
||||
Options *jsonutils.JSONDict `json:"options"`
|
||||
}
|
||||
|
||||
type DnsTrafficPolicyDetails struct {
|
||||
apis.EnabledStatusInfrasResourceBaseDetails
|
||||
SDnsTrafficPolicy
|
||||
}
|
||||
|
||||
type DnsTrafficPolicyListInput struct {
|
||||
apis.EnabledStatusInfrasResourceBaseListInput
|
||||
|
||||
Provider []string `json:"provider"`
|
||||
PolicyType string `json:"policy_type"`
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
// 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 compute
|
||||
|
||||
import "yunion.io/x/onecloud/pkg/apis"
|
||||
|
||||
const (
|
||||
DNS_ZONE_CACHE_STATUS_DELETING = "deleting"
|
||||
DNS_ZONE_CACHE_STATUS_DELETE_FAILED = "delete_failed"
|
||||
DNS_ZONE_CACHE_STATUS_CREATING = "creating"
|
||||
DNS_ZONE_CACHE_STATUS_CREATE_FAILED = "create_failed"
|
||||
DNS_ZONE_CACHE_STATUS_AVAILABLE = "available"
|
||||
DNS_ZONE_CACHE_STATUS_UNKNOWN = "unknown"
|
||||
)
|
||||
|
||||
type DnsZoneCacheCreateInput struct {
|
||||
}
|
||||
|
||||
type DnsZoneCacheDetails struct {
|
||||
apis.StatusStandaloneResourceDetails
|
||||
SDnsZoneCache
|
||||
|
||||
Account string
|
||||
Brand string
|
||||
Provider string
|
||||
}
|
||||
|
||||
type DnsZoneCacheListInput struct {
|
||||
apis.StatusStandaloneResourceListInput
|
||||
|
||||
DnsZoneFilterListBase
|
||||
|
||||
CloudaccountId string `json:"cloudaccount_id"`
|
||||
}
|
||||
@@ -73,8 +73,6 @@ type ServerListInput struct {
|
||||
// 列出操作系统为指定值的主机
|
||||
// enum: linux,windows,vmware
|
||||
OsType []string `json:"os_type"`
|
||||
// 操作系统发行版
|
||||
OsDist []string `json:"os_dist"`
|
||||
|
||||
// 对列表结果按照磁盘大小进行排序
|
||||
// enum: asc,desc
|
||||
|
||||
@@ -17,7 +17,7 @@ package compute
|
||||
type HostwireDetails struct {
|
||||
HostJointResourceDetails
|
||||
|
||||
SHostwire
|
||||
SHostwireDeprecated
|
||||
|
||||
// 二层网络名称
|
||||
Wire string `json:"wire"`
|
||||
|
||||
@@ -95,12 +95,6 @@ type SBackupStorage struct {
|
||||
CapacityMb int `json:"capacity_mb"`
|
||||
}
|
||||
|
||||
// SBackupStorageAccessInfo is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SBackupStorageAccessInfo.
|
||||
type SBackupStorageAccessInfo struct {
|
||||
NfsHost string `json:"nfs_host"`
|
||||
NfsSharedDir string `json:"nfs_shared_dir"`
|
||||
}
|
||||
|
||||
// SBaremetalagent is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SBaremetalagent.
|
||||
type SBaremetalagent struct {
|
||||
apis.SStandaloneResourceBase
|
||||
@@ -309,6 +303,7 @@ type SCloudimage struct {
|
||||
type SCloudprovider struct {
|
||||
apis.SEnabledStatusStandaloneResourceBase
|
||||
apis.SProjectizedResourceBase
|
||||
apis.SExternalizedResourceBase
|
||||
SSyncableBaseResource
|
||||
// 云端服务健康状态。例如欠费、项目冻结都属于不健康状态。
|
||||
// | HealthStatus | 说明 |
|
||||
@@ -329,6 +324,9 @@ type SCloudprovider struct {
|
||||
CloudaccountId string `json:"cloudaccount_id"`
|
||||
// 云账号的平台信息
|
||||
Provider string `json:"provider"`
|
||||
// 云上同步资源是否在本地被更改过配置, local: 更改过, cloud: 未更改过
|
||||
// example: local
|
||||
ProjectSrc string `json:"project_src"`
|
||||
SProjectMappingResourceBase
|
||||
}
|
||||
|
||||
@@ -352,9 +350,9 @@ type SCloudproviderQuota struct {
|
||||
QuotaRange string `json:"quota_range"`
|
||||
// 已使用的配额
|
||||
// -1代表未从云平台拿到已使用配额信息
|
||||
UsedCount int `json:"used_count"`
|
||||
UsedCount int64 `json:"used_count"`
|
||||
// 最大配额限制
|
||||
MaxCount int `json:"max_count"`
|
||||
MaxCount int64 `json:"max_count"`
|
||||
// 配额类型
|
||||
QuotaType string `json:"quota_type"`
|
||||
}
|
||||
@@ -630,6 +628,12 @@ type SDisk struct {
|
||||
// swap: 交换盘
|
||||
// example: sys
|
||||
DiskType string `json:"disk_type"`
|
||||
// 预分配策略
|
||||
// off: 关闭预分配,默认关闭
|
||||
// metadata: 精简制备
|
||||
// falloc: 厚制制备延迟置零
|
||||
// full: 厚制备快速置零
|
||||
Preallocation string `json:"preallocation"`
|
||||
// # is persistent
|
||||
Nonpersistent bool `json:"nonpersistent"`
|
||||
// 是否标记为SSD磁盘
|
||||
@@ -664,48 +668,29 @@ type SDiskResourceBase struct {
|
||||
DiskId string `json:"disk_id"`
|
||||
}
|
||||
|
||||
// SDnsRecordis an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SDnsRecord.
|
||||
// SDnsRecord is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SDnsRecord.
|
||||
type SDnsRecord struct {
|
||||
apis.SEnabledStatusStandaloneResourceBase
|
||||
apis.SExternalizedResourceBase
|
||||
SDnsZoneResourceBase
|
||||
DnsType string `json:"dns_type"`
|
||||
DnsValue string `json:"dns_value"`
|
||||
TTL int64 `json:"ttl"`
|
||||
MxPriority int64 `json:"mx_priority"`
|
||||
}
|
||||
|
||||
// SDnsRecordSetTrafficPolicy is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SDnsRecordSetTrafficPolicy.
|
||||
type SDnsRecordSetTrafficPolicy struct {
|
||||
apis.SJointResourceBase
|
||||
DnsRecordsetId string `json:"dns_recordset_id"`
|
||||
DnsTrafficPolicyId string `json:"dns_traffic_policy_id"`
|
||||
}
|
||||
|
||||
// SDnsTrafficPolicy is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SDnsTrafficPolicy.
|
||||
type SDnsTrafficPolicy struct {
|
||||
apis.SEnabledStatusInfrasResourceBase
|
||||
Provider string `json:"provider"`
|
||||
PolicyType string `json:"policy_type"`
|
||||
PolicyValue string `json:"policy_value"`
|
||||
Options *jsonutils.JSONDict `json:"options"`
|
||||
// 解析线路类型
|
||||
PolicyType string `json:"policy_type"`
|
||||
// 解析线路
|
||||
PolicyValue string `json:"policy_value"`
|
||||
}
|
||||
|
||||
// SDnsZone is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SDnsZone.
|
||||
type SDnsZone struct {
|
||||
apis.SEnabledStatusInfrasResourceBase
|
||||
IsDirty bool `json:"is_dirty"`
|
||||
ZoneType string `json:"zone_type"`
|
||||
Options *jsonutils.JSONDict `json:"options"`
|
||||
}
|
||||
|
||||
// SDnsZoneCache is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SDnsZoneCache.
|
||||
type SDnsZoneCache struct {
|
||||
apis.SStatusStandaloneResourceBase
|
||||
apis.SSharableVirtualResourceBase
|
||||
apis.SEnabledResourceBase
|
||||
apis.SExternalizedResourceBase
|
||||
SDnsZoneResourceBase
|
||||
// 归属云账号ID
|
||||
CloudaccountId string `json:"cloudaccount_id"`
|
||||
ProductType string `json:"product_type"`
|
||||
SManagedResourceBase
|
||||
ZoneType string `json:"zone_type"`
|
||||
ProductType string `json:"product_type"`
|
||||
}
|
||||
|
||||
// SDnsZoneResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SDnsZoneResourceBase.
|
||||
@@ -770,10 +755,12 @@ type SElasticSearch struct {
|
||||
type SElasticcache struct {
|
||||
apis.SVirtualResourceBase
|
||||
apis.SExternalizedResourceBase
|
||||
SCloudregionResourceBase
|
||||
SManagedResourceBase
|
||||
SBillingResourceBase
|
||||
SDeletePreventableResourceBase
|
||||
SVpcResourceBase
|
||||
SZoneResourceBase
|
||||
VpcId string `json:"vpc_id"`
|
||||
// 备可用区
|
||||
SlaveZones string `json:"slave_zones"`
|
||||
// 实例规格
|
||||
@@ -801,9 +788,9 @@ type SElasticcache struct {
|
||||
NetworkId string `json:"network_id"`
|
||||
// 带宽
|
||||
Bandwidth int `json:"bandwidth"`
|
||||
// 内网DNS
|
||||
// 内网DNS
|
||||
PrivateDNS string `json:"private_dns"`
|
||||
// 内网IP地址
|
||||
// 内网IP地址
|
||||
PrivateIpAddr string `json:"private_ip_addr"`
|
||||
// 内网访问端口
|
||||
PrivateConnectPort int `json:"private_connect_port"`
|
||||
@@ -988,6 +975,9 @@ type SExternalProject struct {
|
||||
apis.SVirtualResourceBase
|
||||
apis.SExternalizedResourceBase
|
||||
SManagedResourceBase
|
||||
// 优先级,同一个本地项目映射多个云上项目,优先级高的优先选择
|
||||
// 数值越高,优先级越大
|
||||
Priority int `json:"priority"`
|
||||
ExternalDomainId string `json:"external_domain_id"`
|
||||
// 归属云账号ID
|
||||
CloudaccountId string `json:"cloudaccount_id"`
|
||||
@@ -1087,8 +1077,10 @@ type SGuest struct {
|
||||
SHostResourceBase
|
||||
apis.SEncryptedResource
|
||||
// CPU大小
|
||||
CpuSockets int `json:"cpu_sockets"`
|
||||
// CPU大小
|
||||
VcpuCount int `json:"vcpu_count"`
|
||||
// 内存大小, 单位Mb
|
||||
// 内存大小, 单位MB
|
||||
VmemSize int `json:"vmem_size"`
|
||||
// 启动顺序
|
||||
BootOrder string `json:"boot_order"`
|
||||
@@ -1133,6 +1125,8 @@ type SGuest struct {
|
||||
QgaStatus string `json:"qga_status"`
|
||||
// power_states limit in [on, off, unknown]
|
||||
PowerStates string `json:"power_states"`
|
||||
// Used for guest rescue
|
||||
RescueMode bool `json:"rescue_mode"`
|
||||
}
|
||||
|
||||
// SGuestJointsBase is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SGuestJointsBase.
|
||||
@@ -1206,6 +1200,12 @@ type SGuestnetwork struct {
|
||||
NumQueues int `json:"num_queues"`
|
||||
// 带宽限制,单位mbps
|
||||
BwLimit int `json:"bw_limit"`
|
||||
// 下行流量限制,单位 bytes
|
||||
RxTrafficLimit int64 `json:"rx_traffic_limit"`
|
||||
RxTrafficUsed int64 `json:"rx_traffic_used"`
|
||||
// 上行流量限制,单位 bytes
|
||||
TxTrafficLimit int64 `json:"tx_traffic_limit"`
|
||||
TxTrafficUsed int64 `json:"tx_traffic_used"`
|
||||
// 网卡序号
|
||||
Index byte `json:"index"`
|
||||
// 是否为虚拟接口(无IP)
|
||||
@@ -1342,6 +1342,8 @@ type SHostnetwork struct {
|
||||
IpAddr string `json:"ip_addr"`
|
||||
// MAC地址
|
||||
MacAddr string `json:"mac_addr"`
|
||||
// VLAN
|
||||
VlanId int `json:"vlan_id"`
|
||||
}
|
||||
|
||||
// SHostschedtag is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SHostschedtag.
|
||||
@@ -1368,8 +1370,8 @@ type SHoststorage struct {
|
||||
RealCapacity int64 `json:"real_capacity"`
|
||||
}
|
||||
|
||||
// SHostwire is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SHostwire.
|
||||
type SHostwire struct {
|
||||
// SHostwireDeprecated is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SHostwireDeprecated.
|
||||
type SHostwireDeprecated struct {
|
||||
SHostJointsBase
|
||||
Bridge string `json:"bridge"`
|
||||
// 接口名称
|
||||
@@ -1516,7 +1518,25 @@ type SIsolatedDevice struct {
|
||||
// guest disk index
|
||||
DiskIndex byte `json:"disk_index"`
|
||||
// # pci address of `Bus:Device.Function` format, or usb bus address of `bus.addr`
|
||||
Addr string `json:"addr"`
|
||||
Addr string `json:"addr"`
|
||||
// Is vgpu physical funcion, That means it cannot be attached to guest
|
||||
// VGPUPhysicalFunction bool `nullable:"true" default:"false" list:"domain" create:"domain_optional"`
|
||||
// nvidia vgpu config
|
||||
// vgpu uuid generated on create
|
||||
MdevId string `json:"mdev_id"`
|
||||
// The frame rate limiter (FRL) configuration in frames per second
|
||||
FRL string `json:"frl"`
|
||||
// The frame buffer size in Mbytes
|
||||
Framebuffer string `json:"framebuffer"`
|
||||
// The maximum resolution per display head, eg: 5120x2880
|
||||
MaxResolution string `json:"max_resolution"`
|
||||
// The maximum number of virtual display heads that the vGPU type supports
|
||||
// In computer graphics and display technology, the term "head" is commonly used to
|
||||
// describe the physical interface of a display device or display output.
|
||||
// It refers to a connection point on the monitor, such as HDMI, DisplayPort, or VGA interface.
|
||||
NumHeads string `json:"num_heads"`
|
||||
// The maximum number of vGPU instances per physical GPU
|
||||
MaxInstance string `json:"max_instance"`
|
||||
VendorDeviceId string `json:"vendor_device_id"`
|
||||
// reserved memory size for isolated device
|
||||
ReservedMemory int `json:"reserved_memory"`
|
||||
@@ -1524,6 +1544,8 @@ type SIsolatedDevice struct {
|
||||
ReservedCpu int `json:"reserved_cpu"`
|
||||
// reserved storage size for isolated device
|
||||
ReservedStorage int `json:"reserved_storage"`
|
||||
// PciInfo stores extra PCIE information
|
||||
PcieInfo *IsolatedDevicePCIEInfo `json:"pcie_info"`
|
||||
}
|
||||
|
||||
// SIsolatedDeviceModel is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SIsolatedDeviceModel.
|
||||
@@ -1573,6 +1595,7 @@ type SKafka struct {
|
||||
// SKeypair is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SKeypair.
|
||||
type SKeypair struct {
|
||||
apis.SUserResourceBase
|
||||
apis.SSharableBaseResource
|
||||
// 加密类型
|
||||
// example: RSA
|
||||
Scheme string `json:"scheme"`
|
||||
@@ -1855,9 +1878,9 @@ type SLoadbalancerHealthCheck struct {
|
||||
// 健康检查路径 /
|
||||
HealthCheckHttpCode string `json:"health_check_http_code"`
|
||||
HealthCheckRise int `json:"health_check_rise"`
|
||||
// 健康检查健康阈值 3秒
|
||||
// 健康检查健康阈值 3秒
|
||||
HealthCheckFall int `json:"health_check_fall"`
|
||||
// 健康检查不健康阈值 15秒
|
||||
// 健康检查不健康阈值 15秒
|
||||
HealthCheckTimeout int `json:"health_check_timeout"`
|
||||
// 健康检查超时时间 10秒
|
||||
HealthCheckInterval int `json:"health_check_interval"`
|
||||
@@ -2210,6 +2233,14 @@ type SNetwork struct {
|
||||
BgpType string `json:"bgp_type"`
|
||||
}
|
||||
|
||||
// SNetworkAdditionalWire is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SNetworkAdditionalWire.
|
||||
type SNetworkAdditionalWire struct {
|
||||
NetworkId string `json:"network_id"`
|
||||
WireId string `json:"wire_id"`
|
||||
Synced *bool `json:"synced"`
|
||||
Marked *bool `json:"marked"`
|
||||
}
|
||||
|
||||
// SNetworkAddress is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SNetworkAddress.
|
||||
type SNetworkAddress struct {
|
||||
apis.SStandaloneAnonResourceBase
|
||||
@@ -2480,7 +2511,7 @@ type SSchedtag struct {
|
||||
apis.SStandaloneResourceBase
|
||||
apis.SScopedResourceBase
|
||||
DefaultStrategy string `json:"default_strategy"`
|
||||
// Column(VARCHAR(16, charset='ascii'), nullable=True, default=”)
|
||||
// Column(VARCHAR(16, charset='ascii'), nullable=True, default='')
|
||||
ResourceType string `json:"resource_type"`
|
||||
}
|
||||
|
||||
@@ -2499,21 +2530,12 @@ type SSchedtagResourceBase struct {
|
||||
// SSecurityGroup is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SSecurityGroup.
|
||||
type SSecurityGroup struct {
|
||||
apis.SSharableVirtualResourceBase
|
||||
IsDirty bool `json:"is_dirty"`
|
||||
}
|
||||
|
||||
// SSecurityGroupCache is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SSecurityGroupCache.
|
||||
type SSecurityGroupCache struct {
|
||||
apis.SStatusStandaloneResourceBase
|
||||
apis.SExternalizedResourceBase
|
||||
SCloudregionResourceBase
|
||||
IsDirty bool `json:"is_dirty"`
|
||||
SManagedResourceBase
|
||||
SSecurityGroupResourceBase
|
||||
// 被其他安全组引用的次数
|
||||
ReferenceCount int `json:"reference_count"`
|
||||
// 虚拟私有网络外部Id
|
||||
VpcId string `json:"vpc_id"`
|
||||
ExternalProjectId string `json:"external_project_id"`
|
||||
SCloudregionResourceBase
|
||||
SGlobalVpcResourceBase
|
||||
SVpcResourceBase
|
||||
}
|
||||
|
||||
// SSecurityGroupResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SSecurityGroupResourceBase.
|
||||
@@ -2525,9 +2547,11 @@ type SSecurityGroupResourceBase struct {
|
||||
// SSecurityGroupRule is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SSecurityGroupRule.
|
||||
type SSecurityGroupRule struct {
|
||||
apis.SResourceBase
|
||||
apis.SStatusResourceBase
|
||||
apis.SExternalizedResourceBase
|
||||
SSecurityGroupResourceBase
|
||||
Id string `json:"id"`
|
||||
Priority int64 `json:"priority"`
|
||||
Priority int `json:"priority"`
|
||||
Protocol string `json:"protocol"`
|
||||
Ports string `json:"ports"`
|
||||
Direction string `json:"direction"`
|
||||
@@ -2881,6 +2905,7 @@ type SWafRuleStatement struct {
|
||||
type SWire struct {
|
||||
apis.SStatusInfrasResourceBase
|
||||
apis.SExternalizedResourceBase
|
||||
SManagedResourceBase
|
||||
SVpcResourceBase
|
||||
SZoneResourceBase
|
||||
// 带宽大小, 单位Mbps
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
|
||||
jsonutils "yunion.io/x/jsonutils"
|
||||
|
||||
apis "yunion.io/x/onecloud/pkg/apis"
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
tagutils "yunion.io/x/onecloud/pkg/util/tagutils"
|
||||
)
|
||||
|
||||
@@ -63,6 +63,7 @@ type SCredential struct {
|
||||
// SDomain is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SDomain.
|
||||
type SDomain struct {
|
||||
apis.SStandaloneResourceBase
|
||||
apis.SPendingDeletedBase
|
||||
// 额外信息
|
||||
Extra *jsonutils.JSONDict `json:"extra"`
|
||||
// 改域是否启用
|
||||
@@ -77,7 +78,7 @@ type SDomain struct {
|
||||
// SEnabledIdentityBaseResource is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SEnabledIdentityBaseResource.
|
||||
type SEnabledIdentityBaseResource struct {
|
||||
SIdentityBaseResource
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
apis.SEnabledResourceBase
|
||||
}
|
||||
|
||||
// SEndpoint is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SEndpoint.
|
||||
@@ -129,6 +130,7 @@ type SGroup struct {
|
||||
type SIdentityBaseResource struct {
|
||||
apis.SStandaloneResourceBase
|
||||
apis.SDomainizedResourceBase
|
||||
apis.SPendingDeletedBase
|
||||
// 额外信息
|
||||
Extra *jsonutils.JSONDict `json:"extra"`
|
||||
}
|
||||
@@ -200,7 +202,9 @@ type SNonlocalUser struct {
|
||||
|
||||
// SOrganization is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SOrganization.
|
||||
type SOrganization struct {
|
||||
apis.SEnabledStatusInfrasResourceBase
|
||||
SEnabledIdentityBaseResource
|
||||
apis.SSharableBaseResource
|
||||
apis.SStatusResourceBase
|
||||
Type string `json:"type"`
|
||||
Keys string `json:"keys"`
|
||||
Level int `json:"level"`
|
||||
@@ -208,17 +212,19 @@ type SOrganization struct {
|
||||
|
||||
// SOrganizationDetails is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SOrganizationDetails.
|
||||
type SOrganizationDetails struct {
|
||||
apis.EnabledStatusInfrasResourceBaseDetails
|
||||
EnabledIdentityBaseResourceDetails
|
||||
apis.SharableResourceBaseInfo
|
||||
SOrganization
|
||||
}
|
||||
|
||||
// SOrganizationNode is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SOrganizationNode.
|
||||
type SOrganizationNode struct {
|
||||
apis.SStandaloneResourceBase
|
||||
apis.SPendingDeletedBase
|
||||
OrgId string `json:"org_id"`
|
||||
FullLabel string `json:"full_label"`
|
||||
Level int `json:"level"`
|
||||
Weight *int `json:"weight"`
|
||||
Weight int `json:"weight"`
|
||||
}
|
||||
|
||||
// SOrganizationNodeDetails is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SOrganizationNodeDetails.
|
||||
@@ -339,6 +345,19 @@ type SServiceCertificate struct {
|
||||
CaPrivateKey string `json:"ca_private_key"`
|
||||
}
|
||||
|
||||
// STokenCache is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.STokenCache.
|
||||
type STokenCache struct {
|
||||
apis.SStandaloneAnonResourceBase
|
||||
Valid bool `json:"valid"`
|
||||
Method string `json:"method"`
|
||||
AuditIds string `json:"audit_ids"`
|
||||
UserId string `json:"user_id"`
|
||||
ProjectId string `json:"project_id"`
|
||||
DomainId string `json:"domain_id"`
|
||||
Source string `json:"source"`
|
||||
Ip string `json:"ip"`
|
||||
}
|
||||
|
||||
// SUser is an autogenerated struct via yunion.io/x/onecloud/pkg/keystone/models.SUser.
|
||||
type SUser struct {
|
||||
apis.SRecordChecksumResourceBase
|
||||
|
||||
@@ -66,6 +66,8 @@ type GuestImageListInput struct {
|
||||
|
||||
// 是否删除保护
|
||||
Protected *bool `json:"protected"`
|
||||
|
||||
DiskFormat []string `json:"disk_format"`
|
||||
}
|
||||
|
||||
type ImageDetails struct {
|
||||
|
||||
@@ -152,6 +152,7 @@ type SJointResourceBase struct {
|
||||
// SKeystoneCacheObject is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudcommon/db.SKeystoneCacheObject.
|
||||
type SKeystoneCacheObject struct {
|
||||
SStandaloneResourceBase
|
||||
SPendingDeletedBase
|
||||
DomainId string `json:"domain_id"`
|
||||
Domain string `json:"domain"`
|
||||
Lang string `json:"lang"`
|
||||
@@ -212,6 +213,14 @@ type SOpsLog struct {
|
||||
OwnerProjectId string `json:"owner_tenant_id"`
|
||||
}
|
||||
|
||||
// SPendingDeletedBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudcommon/db.SPendingDeletedBase.
|
||||
type SPendingDeletedBase struct {
|
||||
// 资源放入回收站时间
|
||||
PendingDeletedAt time.Time `json:"pending_deleted_at"`
|
||||
// 资源是否处于回收站中
|
||||
PendingDeleted bool `json:"pending_deleted"`
|
||||
}
|
||||
|
||||
// SProjectizedResourceBase is an autogenerated struct via yunion.io/x/onecloud/pkg/cloudcommon/db.SProjectizedResourceBase.
|
||||
type SProjectizedResourceBase struct {
|
||||
SDomainizedResourceBase
|
||||
@@ -354,15 +363,12 @@ type SVirtualJointResourceBase struct {
|
||||
type SVirtualResourceBase struct {
|
||||
SStatusStandaloneResourceBase
|
||||
SProjectizedResourceBase
|
||||
SPendingDeletedBase
|
||||
// 云上同步资源是否在本地被更改过配置, local: 更改过, cloud: 未更改过
|
||||
// example: local
|
||||
ProjectSrc string `json:"project_src"`
|
||||
// 是否是系统资源
|
||||
IsSystem bool `json:"is_system"`
|
||||
// 资源放入回收站时间
|
||||
PendingDeletedAt time.Time `json:"pending_deleted_at"`
|
||||
// 资源是否处于回收站中
|
||||
PendingDeleted bool `json:"pending_deleted"`
|
||||
// 资源是否被冻结
|
||||
Freezed bool `json:"freezed"`
|
||||
}
|
||||
|
||||
@@ -544,7 +544,6 @@ func (manager *SMetadataManager) rawSetValues(ctx context.Context, objType strin
|
||||
keys := make([]string, 0, len(store))
|
||||
changes := make([]sMetadataChange, 0)
|
||||
for key, value := range store {
|
||||
key = strings.TrimSpace(key)
|
||||
keys = append(keys, key)
|
||||
|
||||
record := SMetadata{}
|
||||
@@ -709,7 +708,7 @@ func (manager *SMetadataManager) rawGetAll(objType, objId string, keys []string,
|
||||
ret := make(map[string]string)
|
||||
for _, rec := range records {
|
||||
if len(rec.Value) > 0 || strings.HasPrefix(rec.Key, USER_TAG_PREFIX) || strings.HasPrefix(rec.Key, CLOUD_TAG_PREFIX) {
|
||||
ret[strings.TrimSpace(rec.Key)] = rec.Value
|
||||
ret[rec.Key] = rec.Value
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
@@ -772,7 +771,7 @@ func metaList2Map(manager IMetadataBaseModelManager, userCred mcclient.TokenCred
|
||||
hiddenKeys := manager.GetMetadataHiddenKeys()
|
||||
for _, meta := range metaList {
|
||||
if IsMetadataKeyVisible(meta.Key) && !utils.IsInStringArray(meta.Key, hiddenKeys) {
|
||||
metaMap[strings.TrimSpace(meta.Key)] = meta.Value
|
||||
metaMap[meta.Key] = meta.Value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,12 @@ package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/gotypes"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/utils"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
@@ -40,17 +40,14 @@ type SBackupStorageManager struct {
|
||||
type SBackupStorage struct {
|
||||
db.SEnabledStatusInfrasResourceBase
|
||||
|
||||
AccessInfo *SBackupStorageAccessInfo
|
||||
StorageType string `width:"32" charset:"ascii" nullable:"false" list:"user" create:"domain_required"`
|
||||
CapacityMb int `nullable:"false" list:"user" update:"domain" create:"domain_required"`
|
||||
AccessInfo *api.SBackupStorageAccessInfo
|
||||
StorageType api.TBackupStorageType `width:"32" charset:"ascii" nullable:"false" list:"user" create:"domain_required"`
|
||||
CapacityMb int `nullable:"false" list:"user" update:"domain" create:"domain_required"`
|
||||
}
|
||||
|
||||
var BackupStorageManager *SBackupStorageManager
|
||||
|
||||
func init() {
|
||||
gotypes.RegisterSerializable(reflect.TypeOf(&SBackupStorageAccessInfo{}), func() gotypes.ISerializable {
|
||||
return &SBackupStorageAccessInfo{}
|
||||
})
|
||||
BackupStorageManager = &SBackupStorageManager{
|
||||
SEnabledStatusInfrasResourceBaseManager: db.NewEnabledStatusInfrasResourceBaseManager(
|
||||
SBackupStorage{},
|
||||
@@ -62,48 +59,58 @@ func init() {
|
||||
BackupStorageManager.SetVirtualObject(BackupStorageManager)
|
||||
}
|
||||
|
||||
type SBackupStorageAccessInfo struct {
|
||||
NfsHost string `json:"nfs_host"`
|
||||
NfsSharedDir string `json:"nfs_shared_dir"`
|
||||
}
|
||||
|
||||
func (ba *SBackupStorageAccessInfo) String() string {
|
||||
return jsonutils.Marshal(ba).String()
|
||||
}
|
||||
|
||||
func (ba *SBackupStorageAccessInfo) IsZero() bool {
|
||||
return ba == nil
|
||||
}
|
||||
|
||||
func (bs *SBackupStorageManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.BackupStorageCreateInput) (api.BackupStorageCreateInput, error) {
|
||||
var err error
|
||||
input.EnabledStatusInfrasResourceBaseCreateInput, err = bs.SEnabledStatusInfrasResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.EnabledStatusInfrasResourceBaseCreateInput)
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
if !utils.IsInStringArray(input.StorageType, []string{api.BACKUPSTORAGE_TYPE_NFS}) {
|
||||
if !utils.IsInArray(input.StorageType, []string{string(api.BACKUPSTORAGE_TYPE_NFS), string(api.BACKUPSTORAGE_TYPE_OBJECT_STORAGE)}) {
|
||||
return input, httperrors.NewInputParameterError("Invalid storage type %s", input.StorageType)
|
||||
}
|
||||
switch input.StorageType {
|
||||
case api.BACKUPSTORAGE_TYPE_NFS:
|
||||
case string(api.BACKUPSTORAGE_TYPE_NFS):
|
||||
if input.NfsHost == "" {
|
||||
return input, httperrors.NewInputParameterError("nfs_host is required when storage type is nfs")
|
||||
}
|
||||
if input.NfsSharedDir == "" {
|
||||
return input, httperrors.NewInputParameterError("nfs_shared_dir is required when storage type is nfs")
|
||||
}
|
||||
case string(api.BACKUPSTORAGE_TYPE_OBJECT_STORAGE):
|
||||
if input.ObjectBucketUrl == "" {
|
||||
return input, httperrors.NewInputParameterError("object_bucket_url is required when storage type is object")
|
||||
}
|
||||
_, err := url.Parse(input.ObjectBucketUrl)
|
||||
if err != nil {
|
||||
return input, httperrors.NewInputParameterError("invalid object_bucket_url %s: %s", input.ObjectBucketUrl, err)
|
||||
}
|
||||
if input.ObjectAccessKey == "" {
|
||||
return input, httperrors.NewInputParameterError("object_access_key is required when storage type is object")
|
||||
}
|
||||
if input.ObjectSecret == "" {
|
||||
return input, httperrors.NewInputParameterError("object_secret is required when storage type is object")
|
||||
}
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
func (bs *SBackupStorage) CustomizeCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
|
||||
bs.SetEnabled(true)
|
||||
nfsHost, _ := data.GetString("nfs_host")
|
||||
nfsSharedDir, _ := data.GetString("nfs_shared_dir")
|
||||
input := api.BackupStorageCreateInput{}
|
||||
err := data.Unmarshal(&input)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Unmarshal BackupStorageCreateInput")
|
||||
}
|
||||
// nfsHost, _ := data.GetString("nfs_host")
|
||||
// nfsSharedDir, _ := data.GetString("nfs_shared_dir")
|
||||
bs.Status = api.BACKUPSTORAGE_STATUS_ONLINE
|
||||
bs.AccessInfo = &SBackupStorageAccessInfo{
|
||||
NfsHost: nfsHost,
|
||||
NfsSharedDir: nfsSharedDir,
|
||||
bs.AccessInfo = &api.SBackupStorageAccessInfo{
|
||||
NfsHost: input.NfsHost,
|
||||
NfsSharedDir: input.NfsSharedDir,
|
||||
|
||||
ObjectBucketUrl: input.ObjectBucketUrl,
|
||||
ObjectAccessKey: input.ObjectAccessKey,
|
||||
ObjectSecret: input.ObjectSecret,
|
||||
}
|
||||
return bs.SEnabledStatusInfrasResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data)
|
||||
}
|
||||
@@ -118,23 +125,47 @@ func (bs *SBackupStorage) ValidateDeleteCondition(ctx context.Context, info json
|
||||
return httperrors.NewInternalServerError("BackupCount fail %s", err)
|
||||
}
|
||||
if cnt > 0 {
|
||||
return httperrors.NewNotEmptyError("storage has backup")
|
||||
return httperrors.NewNotEmptyError("storage has been used")
|
||||
}
|
||||
return bs.SEnabledStatusInfrasResourceBase.ValidateDeleteCondition(ctx, nil)
|
||||
}
|
||||
|
||||
func (bs *SBackupStorage) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
||||
bs.SEnabledStatusInfrasResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
|
||||
bs.SetStatus(userCred, api.BACKUPSTORAGE_STATUS_OFFLINE, "")
|
||||
if bs.StorageType == api.BACKUPSTORAGE_TYPE_OBJECT_STORAGE {
|
||||
err := bs.saveObjectSecret(bs.AccessInfo.ObjectSecret)
|
||||
if err != nil {
|
||||
log.Errorf("convert object secret fail %s", err)
|
||||
}
|
||||
}
|
||||
err := StartResourceSyncStatusTask(ctx, userCred, bs, "BackupStorageSyncstatusTask", "")
|
||||
if err != nil {
|
||||
log.Errorf("unable to sync backup storage status")
|
||||
}
|
||||
bs.SetStatus(userCred, api.BACKUPSTORAGE_STATUS_OFFLINE, "")
|
||||
}
|
||||
|
||||
func (bs *SBackupStorage) saveObjectSecret(secret string) error {
|
||||
sec, err := utils.EncryptAESBase64(bs.Id, secret)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "EncryptAESBase64")
|
||||
}
|
||||
accessInfo := *bs.AccessInfo
|
||||
accessInfo.ObjectSecret = sec
|
||||
_, err = db.Update(bs, func() error {
|
||||
bs.AccessInfo = &accessInfo
|
||||
return nil
|
||||
})
|
||||
return errors.Wrap(err, "Update")
|
||||
}
|
||||
|
||||
func (bs *SBackupStorage) getMoreDetails(ctx context.Context, out api.BackupStorageDetails) api.BackupStorageDetails {
|
||||
out.NfsHost = bs.AccessInfo.NfsHost
|
||||
out.NfsSharedDir = bs.AccessInfo.NfsSharedDir
|
||||
out.ObjectBucketUrl = bs.AccessInfo.ObjectBucketUrl
|
||||
out.ObjectAccessKey = bs.AccessInfo.ObjectAccessKey
|
||||
// should not return secret
|
||||
out.ObjectSecret = "" // bs.AccessInfo.ObjectSecret
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -162,9 +193,9 @@ func (bm *SBackupStorageManager) ListItemFilter(ctx context.Context, q *sqlchemy
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (self *SBackupStorage) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.DiskBackupSyncstatusInput) (jsonutils.JSONObject, error) {
|
||||
func (bs *SBackupStorage) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.DiskBackupSyncstatusInput) (jsonutils.JSONObject, error) {
|
||||
var openTask = true
|
||||
count, err := taskman.TaskManager.QueryTasksOfObject(self, time.Now().Add(-3*time.Minute), &openTask).CountWithError()
|
||||
count, err := taskman.TaskManager.QueryTasksOfObject(bs, time.Now().Add(-3*time.Minute), &openTask).CountWithError()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -172,5 +203,94 @@ func (self *SBackupStorage) PerformSyncstatus(ctx context.Context, userCred mccl
|
||||
return nil, httperrors.NewBadRequestError("Backup has %d task active, can't sync status", count)
|
||||
}
|
||||
|
||||
return nil, StartResourceSyncStatusTask(ctx, userCred, self, "BackupStorageSyncstatusTask", "")
|
||||
return nil, StartResourceSyncStatusTask(ctx, userCred, bs, "BackupStorageSyncstatusTask", "")
|
||||
}
|
||||
|
||||
func (bs *SBackupStorage) ValidateUpdateData(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
query jsonutils.JSONObject,
|
||||
input api.BackupStorageUpdateInput,
|
||||
) (api.BackupStorageUpdateInput, error) {
|
||||
var err error
|
||||
if len(input.Name) > 0 {
|
||||
err := isValidBucketName(input.Name)
|
||||
if err != nil {
|
||||
return input, httperrors.NewInputParameterError("invalid bucket name(%s): %s", input.Name, err)
|
||||
}
|
||||
}
|
||||
input.EnabledStatusInfrasResourceBaseUpdateInput, err = bs.SEnabledStatusInfrasResourceBase.ValidateUpdateData(ctx, userCred, query, input.EnabledStatusInfrasResourceBaseUpdateInput)
|
||||
if err != nil {
|
||||
return input, errors.Wrap(err, "SSharableVirtualResourceBase.ValidateUpdateData")
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
func (bs *SBackupStorage) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
||||
bs.SEnabledStatusInfrasResourceBase.PostUpdate(ctx, userCred, query, data)
|
||||
input := api.BackupStorageUpdateInput{}
|
||||
err := data.Unmarshal(&input)
|
||||
if err != nil {
|
||||
log.Errorf("SBackupStorage.PostUpdate Unmarshal data %s fail %s", data, err)
|
||||
return
|
||||
}
|
||||
// update accessinfo
|
||||
accessInfoChanged := false
|
||||
accessInfo := *bs.AccessInfo
|
||||
switch bs.StorageType {
|
||||
case api.BACKUPSTORAGE_TYPE_NFS:
|
||||
if len(input.NfsHost) > 0 {
|
||||
accessInfo.NfsHost = input.NfsHost
|
||||
accessInfoChanged = true
|
||||
}
|
||||
if len(input.NfsSharedDir) > 0 {
|
||||
accessInfo.NfsSharedDir = input.NfsSharedDir
|
||||
accessInfoChanged = true
|
||||
}
|
||||
case api.BACKUPSTORAGE_TYPE_OBJECT_STORAGE:
|
||||
if len(input.ObjectBucketUrl) > 0 {
|
||||
accessInfo.ObjectBucketUrl = input.ObjectBucketUrl
|
||||
accessInfoChanged = true
|
||||
}
|
||||
if len(input.ObjectAccessKey) > 0 {
|
||||
accessInfo.ObjectAccessKey = input.ObjectAccessKey
|
||||
accessInfoChanged = true
|
||||
}
|
||||
if len(input.ObjectSecret) > 0 {
|
||||
sec, err := utils.EncryptAESBase64(bs.Id, input.ObjectSecret)
|
||||
if err != nil {
|
||||
log.Errorf("EncryptAESBase64 fail %s", err)
|
||||
return
|
||||
}
|
||||
accessInfo.ObjectSecret = sec
|
||||
accessInfoChanged = true
|
||||
}
|
||||
}
|
||||
if accessInfoChanged {
|
||||
_, err = db.Update(bs, func() error {
|
||||
bs.AccessInfo = &accessInfo
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("update fail %s", err)
|
||||
} else {
|
||||
err := StartResourceSyncStatusTask(ctx, userCred, bs, "BackupStorageSyncstatusTask", "")
|
||||
if err != nil {
|
||||
log.Errorf("unable to sync backup storage status")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bs *SBackupStorage) GetAccessInfo() (*api.SBackupStorageAccessInfo, error) {
|
||||
accessInfo := *bs.AccessInfo
|
||||
switch bs.StorageType {
|
||||
case api.BACKUPSTORAGE_TYPE_OBJECT_STORAGE:
|
||||
secret, err := utils.DescryptAESBase64(bs.Id, accessInfo.ObjectSecret)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "DescryptAESBase64")
|
||||
}
|
||||
accessInfo.ObjectSecret = secret
|
||||
}
|
||||
return &accessInfo, nil
|
||||
}
|
||||
|
||||
@@ -808,10 +808,14 @@ func (self *SDisk) getDiskAllocateFromBackupInput(ctx context.Context, backupId
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to get backupstorage of backup %s", backupId)
|
||||
}
|
||||
accessInfo, err := bs.GetAccessInfo()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "backupStorage.GetAccessInfo")
|
||||
}
|
||||
return &api.DiskAllocateFromBackupInput{
|
||||
BackupId: backupId,
|
||||
BackupStorageId: bs.GetId(),
|
||||
BackupStorageAccessInfo: jsonutils.Marshal(bs.AccessInfo).(*jsonutils.JSONDict),
|
||||
BackupStorageAccessInfo: jsonutils.Marshal(accessInfo).(*jsonutils.JSONDict),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -655,10 +655,6 @@ func (manager *SGuestManager) ListItemFilter(
|
||||
if len(query.OsType) > 0 {
|
||||
q = q.In("os_type", query.OsType)
|
||||
}
|
||||
if len(query.OsDist) > 0 {
|
||||
metaSQ := db.Metadata.Query().Equals("key", "os_distribution").In("value", query.OsDist).SubQuery()
|
||||
q = q.Join(metaSQ, sqlchemy.Equals(q.Field("id"), metaSQ.Field("obj_id")))
|
||||
}
|
||||
if len(query.VcpuCount) > 0 {
|
||||
q = q.In("vcpu_count", query.VcpuCount)
|
||||
}
|
||||
@@ -784,13 +780,6 @@ func (manager *SGuestManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field
|
||||
if err == nil {
|
||||
return q, nil
|
||||
}
|
||||
if field == "os_dist" {
|
||||
metaQuery := db.Metadata.Query("obj_id", "value").Equals("key", "os_distribution").SubQuery()
|
||||
q = q.AppendField(metaQuery.Field("value", field)).Distinct()
|
||||
q = q.Join(metaQuery, sqlchemy.Equals(q.Field("id"), metaQuery.Field("obj_id")))
|
||||
q.GroupBy(metaQuery.Field("value"))
|
||||
return q, nil
|
||||
}
|
||||
guestnets := GuestnetworkManager.Query("guest_id", "network_id").SubQuery()
|
||||
q = q.LeftJoin(guestnets, sqlchemy.Equals(q.Field("id"), guestnets.Field("guest_id")))
|
||||
q, err = manager.SNetworkResourceBaseManager.QueryDistinctExtraField(q, field)
|
||||
|
||||
@@ -225,6 +225,7 @@ func (self *SInstanceBackup) getMoreDetails(userCred mcclient.TokenCredential, o
|
||||
CreatedAt: backups[i].CreatedAt,
|
||||
})
|
||||
}
|
||||
out.Size = self.SizeMb * 1024 * 1024
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -366,6 +367,9 @@ func (self *SInstanceBackup) ToInstanceCreateInput(sourceInput *api.ServerCreate
|
||||
createInput.Disks[i].BackupId = isjs[index].DiskBackupId
|
||||
createInput.Disks[i].ImageId = ""
|
||||
createInput.Disks[i].SnapshotId = ""
|
||||
if i < len(sourceInput.Disks) {
|
||||
createInput.Disks[i].Backend = sourceInput.Disks[i].Backend
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ type SInstanceSnapshot struct {
|
||||
// 套餐名称
|
||||
InstanceType string `width:"64" charset:"utf8" nullable:"true" list:"user" create:"optional"`
|
||||
// 主机快照磁盘容量和
|
||||
SizeMb int `nullable:"false"`
|
||||
SizeMb int `nullable:"false" list:"user"`
|
||||
// 镜像ID
|
||||
ImageId string `width:"36" charset:"ascii" nullable:"true" list:"user"`
|
||||
// 是否保存内存
|
||||
@@ -280,7 +280,7 @@ func (self *SInstanceSnapshot) getMoreDetails(userCred mcclient.TokenCredential,
|
||||
}
|
||||
}
|
||||
} else if guest != nil {
|
||||
out.Size = self.SizeMb
|
||||
out.Size = self.SizeMb * 1024 * 1024
|
||||
disk, err := guest.GetSystemDisk()
|
||||
if err != nil {
|
||||
log.Errorf("unable to GetSystemDisk of guest %q", guest.GetId())
|
||||
|
||||
@@ -1005,7 +1005,11 @@ func (self *SKVMRegionDriver) RequestPackInstanceBackup(ctx context.Context, ib
|
||||
body := jsonutils.NewDict()
|
||||
body.Set("package_name", jsonutils.NewString(packageName))
|
||||
body.Set("backup_storage_id", jsonutils.NewString(backupStorage.GetId()))
|
||||
body.Set("backup_storage_access_info", jsonutils.Marshal(backupStorage.AccessInfo))
|
||||
accessInfo, err := backupStorage.GetAccessInfo()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "GetAccessInfo")
|
||||
}
|
||||
body.Set("backup_storage_access_info", jsonutils.Marshal(accessInfo))
|
||||
body.Set("backup_ids", jsonutils.Marshal(backupIds))
|
||||
body.Set("metadata", jsonutils.Marshal(metadata))
|
||||
header := task.GetTaskRequestHeader()
|
||||
@@ -1031,7 +1035,11 @@ func (self *SKVMRegionDriver) RequestUnpackInstanceBackup(ctx context.Context, i
|
||||
body := jsonutils.NewDict()
|
||||
body.Set("package_name", jsonutils.NewString(packageName))
|
||||
body.Set("backup_storage_id", jsonutils.NewString(backupStorage.GetId()))
|
||||
body.Set("backup_storage_access_info", jsonutils.Marshal(backupStorage.AccessInfo))
|
||||
accessInfo, err := backupStorage.GetAccessInfo()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "GetAccessInfo")
|
||||
}
|
||||
body.Set("backup_storage_access_info", jsonutils.Marshal(accessInfo))
|
||||
if metadataOnly {
|
||||
body.Set("metadata_only", jsonutils.JSONTrue)
|
||||
}
|
||||
@@ -1052,7 +1060,11 @@ func (self *SKVMRegionDriver) RequestSyncBackupStorageStatus(ctx context.Context
|
||||
url := fmt.Sprintf("%s/storages/sync-backup-storage", host.ManagerUri)
|
||||
body := jsonutils.NewDict()
|
||||
body.Set("backup_storage_id", jsonutils.NewString(bs.GetId()))
|
||||
body.Set("backup_storage_access_info", jsonutils.Marshal(bs.AccessInfo))
|
||||
accessInfo, err := bs.GetAccessInfo()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetAccessInfo")
|
||||
}
|
||||
body.Set("backup_storage_access_info", jsonutils.Marshal(accessInfo))
|
||||
header := task.GetTaskRequestHeader()
|
||||
_, res, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, body, false)
|
||||
if err != nil {
|
||||
@@ -1109,7 +1121,7 @@ func (self *SKVMRegionDriver) RequestSyncDiskBackupStatus(ctx context.Context, u
|
||||
if utils.IsInStringArray(originStatus, []string{api.BACKUP_STATUS_CREATING, api.BACKUP_STATUS_SNAPSHOT, api.BACKUP_STATUS_SAVING, api.BACKUP_STATUS_CLEANUP_SNAPSHOT, api.BACKUP_STATUS_DELETING}) {
|
||||
return nil, backup.SetStatus(userCred, originStatus, "sync status")
|
||||
}
|
||||
backupStroage, err := backup.GetBackupStorage()
|
||||
backupStorage, err := backup.GetBackupStorage()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to get backupStorage")
|
||||
}
|
||||
@@ -1128,8 +1140,12 @@ func (self *SKVMRegionDriver) RequestSyncDiskBackupStatus(ctx context.Context, u
|
||||
url := fmt.Sprintf("%s/storages/sync-backup", host.ManagerUri)
|
||||
body := jsonutils.NewDict()
|
||||
body.Set("backup_id", jsonutils.NewString(backup.GetId()))
|
||||
body.Set("backup_storage_id", jsonutils.NewString(backupStroage.GetId()))
|
||||
body.Set("backup_storage_access_info", jsonutils.Marshal(backupStroage.AccessInfo))
|
||||
body.Set("backup_storage_id", jsonutils.NewString(backupStorage.GetId()))
|
||||
accessInfo, err := backupStorage.GetAccessInfo()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetAccessInfo")
|
||||
}
|
||||
body.Set("backup_storage_access_info", jsonutils.Marshal(accessInfo))
|
||||
header := task.GetTaskRequestHeader()
|
||||
_, res, err := httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, body, false)
|
||||
if err != nil {
|
||||
@@ -1332,7 +1348,7 @@ func (self *SKVMRegionDriver) GetMaxElasticcacheSecurityGroupCount() int {
|
||||
}
|
||||
|
||||
func (self *SKVMRegionDriver) RequestDeleteBackup(ctx context.Context, backup *models.SDiskBackup, task taskman.ITask) error {
|
||||
backupStroage, err := backup.GetBackupStorage()
|
||||
backupStorage, err := backup.GetBackupStorage()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to get backupStorage")
|
||||
}
|
||||
@@ -1350,8 +1366,12 @@ func (self *SKVMRegionDriver) RequestDeleteBackup(ctx context.Context, backup *m
|
||||
url := fmt.Sprintf("%s/storages/delete-backup", host.ManagerUri)
|
||||
body := jsonutils.NewDict()
|
||||
body.Set("backup_id", jsonutils.NewString(backup.GetId()))
|
||||
body.Set("backup_storage_id", jsonutils.NewString(backupStroage.GetId()))
|
||||
body.Set("backup_storage_access_info", jsonutils.Marshal(backupStroage.AccessInfo))
|
||||
body.Set("backup_storage_id", jsonutils.NewString(backupStorage.GetId()))
|
||||
accessInfo, err := backupStorage.GetAccessInfo()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "GetAccessInfo")
|
||||
}
|
||||
body.Set("backup_storage_access_info", jsonutils.Marshal(accessInfo))
|
||||
header := task.GetTaskRequestHeader()
|
||||
_, _, err = httputils.JSONRequest(httputils.GetDefaultClient(), ctx, "POST", url, header, body, false)
|
||||
if err != nil {
|
||||
@@ -1361,7 +1381,7 @@ func (self *SKVMRegionDriver) RequestDeleteBackup(ctx context.Context, backup *m
|
||||
}
|
||||
|
||||
func (self *SKVMRegionDriver) RequestCreateBackup(ctx context.Context, backup *models.SDiskBackup, snapshotId string, task taskman.ITask) error {
|
||||
backupStroage, err := backup.GetBackupStorage()
|
||||
backupStorage, err := backup.GetBackupStorage()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to get backupStorage")
|
||||
}
|
||||
@@ -1382,8 +1402,12 @@ func (self *SKVMRegionDriver) RequestCreateBackup(ctx context.Context, backup *m
|
||||
body := jsonutils.NewDict()
|
||||
body.Set("snapshot_id", jsonutils.NewString(snapshotId))
|
||||
body.Set("backup_id", jsonutils.NewString(backup.GetId()))
|
||||
body.Set("backup_storage_id", jsonutils.NewString(backupStroage.GetId()))
|
||||
body.Set("backup_storage_access_info", jsonutils.Marshal(backupStroage.AccessInfo))
|
||||
body.Set("backup_storage_id", jsonutils.NewString(backupStorage.GetId()))
|
||||
accessInfo, err := backupStorage.GetAccessInfo()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "GetAccessInfo")
|
||||
}
|
||||
body.Set("backup_storage_access_info", jsonutils.Marshal(accessInfo))
|
||||
if len(backup.EncryptKeyId) > 0 {
|
||||
body.Set("encrypt_key_id", jsonutils.NewString(backup.EncryptKeyId))
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/compute/models"
|
||||
@@ -35,6 +36,7 @@ func init() {
|
||||
|
||||
func (self *BackupStorageSyncstatusTask) taskFailed(ctx context.Context, bs *models.SBackupStorage, err jsonutils.JSONObject) {
|
||||
logclient.AddActionLogWithContext(ctx, bs, logclient.ACT_SYNC_STATUS, err, self.UserCred, false)
|
||||
bs.SetStatus(self.UserCred, api.BACKUPSTORAGE_STATUS_OFFLINE, err.String())
|
||||
self.SetStageFailed(ctx, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
// 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 storageman
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/qemuimgfmt"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/hostman/options"
|
||||
"yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage"
|
||||
_ "yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage/nfs"
|
||||
_ "yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage/object"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
identity_modules "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
|
||||
"yunion.io/x/onecloud/pkg/util/fileutils2"
|
||||
"yunion.io/x/onecloud/pkg/util/procutils"
|
||||
"yunion.io/x/onecloud/pkg/util/qemuimg"
|
||||
)
|
||||
|
||||
func ensureBackupDir() (string, error) {
|
||||
backupTmpDir := options.HostOptions.LocalBackupTempPath
|
||||
if !fileutils2.Exists(backupTmpDir) {
|
||||
output, err := procutils.NewCommand("mkdir", "-p", backupTmpDir).Output()
|
||||
if err != nil {
|
||||
log.Errorf("mkdir %s failed: %s", backupTmpDir, output)
|
||||
return "", errors.Wrapf(err, "mkdir %s failed: %s", backupTmpDir, output)
|
||||
}
|
||||
}
|
||||
tmpFileDir, err := ioutil.TempDir(backupTmpDir, "backuptmp*")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "ioutil.TempDir")
|
||||
}
|
||||
return tmpFileDir, nil
|
||||
}
|
||||
|
||||
func cleanupDirOrFile(path string) {
|
||||
log.Debugf("cleanup backup %s", path)
|
||||
if output, err := procutils.NewCommand("rm", "-rf", path).Output(); err != nil {
|
||||
log.Errorf("unable to rm %s: %s", path, output)
|
||||
}
|
||||
}
|
||||
|
||||
func doBackupDisk(ctx context.Context, snapshotPath string, diskBackup *SDiskBackup) (int, error) {
|
||||
backupTmpDir, err := ensureBackupDir()
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "ensureBackupDir")
|
||||
}
|
||||
defer cleanupDirOrFile(backupTmpDir)
|
||||
|
||||
backupPath := path.Join(backupTmpDir, diskBackup.BackupId)
|
||||
img, err := qemuimg.NewQemuImage(snapshotPath)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "NewQemuImage snapshot")
|
||||
}
|
||||
encKey := ""
|
||||
if len(diskBackup.EncryptKeyId) > 0 {
|
||||
session := auth.GetSession(ctx, diskBackup.UserCred, consts.GetRegion())
|
||||
secKey, err := identity_modules.Credentials.GetEncryptKey(session, diskBackup.EncryptKeyId)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "GetEncryptKey")
|
||||
}
|
||||
encKey = secKey.Key
|
||||
}
|
||||
if len(encKey) > 0 {
|
||||
img.SetPassword(encKey)
|
||||
}
|
||||
newImage, err := img.Clone(backupPath, qemuimgfmt.QCOW2, true)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "unable to backup snapshot")
|
||||
}
|
||||
|
||||
newImageSizeMb := newImage.GetActualSizeMB()
|
||||
|
||||
backupStorage, err := backupstorage.GetBackupStorage(diskBackup.BackupStorageId, diskBackup.BackupStorageAccessInfo)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "GetBackupStorage")
|
||||
}
|
||||
|
||||
err = backupStorage.SaveBackupFrom(ctx, backupPath, diskBackup.BackupId)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "SaveBackupFrom")
|
||||
}
|
||||
|
||||
return newImageSizeMb, nil
|
||||
}
|
||||
|
||||
func doRestoreDisk(ctx context.Context, diskInfo api.DiskAllocateInput, destImgPath string, format string) error {
|
||||
backupTmpDir, err := ensureBackupDir()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "ensureBackupDir")
|
||||
}
|
||||
defer cleanupDirOrFile(backupTmpDir)
|
||||
|
||||
backupStorage, err := backupstorage.GetBackupStorage(diskInfo.Backup.BackupStorageId, diskInfo.Backup.BackupStorageAccessInfo)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "GetBackupStorage")
|
||||
}
|
||||
backupPath := path.Join(backupTmpDir, diskInfo.Backup.BackupId)
|
||||
err = backupStorage.RestoreBackupTo(ctx, backupPath, diskInfo.Backup.BackupId)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "RestoreBackupTo")
|
||||
}
|
||||
img, err := qemuimg.NewQemuImage(backupPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "NewQemuImage")
|
||||
}
|
||||
if diskInfo.Encryption {
|
||||
img.SetPassword(diskInfo.EncryptInfo.Key)
|
||||
}
|
||||
if len(format) == 0 {
|
||||
format = qemuimgfmt.QCOW2.String()
|
||||
}
|
||||
_, err = img.Clone(destImgPath, qemuimgfmt.String2ImageFormat(format), false)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Clone %s", destImgPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
PackageDiskFilename = "disk"
|
||||
PackageMetadataFilename = "metadata"
|
||||
)
|
||||
|
||||
func DoInstancePackBackup(ctx context.Context, backupInfo SStoragePackInstanceBackup) (string, error) {
|
||||
backupTmpDir, err := ensureBackupDir()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "ensureBackupDir")
|
||||
}
|
||||
defer cleanupDirOrFile(backupTmpDir)
|
||||
|
||||
backupStorage, err := backupstorage.GetBackupStorage(backupInfo.BackupStorageId, backupInfo.BackupStorageAccessInfo)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "GetBackupStorage")
|
||||
}
|
||||
|
||||
packagePath := path.Join(backupTmpDir, backupInfo.PackageName)
|
||||
{
|
||||
// prepare package Path
|
||||
output, err := procutils.NewCommand("mkdir", "-p", packagePath).Output()
|
||||
if err != nil {
|
||||
log.Errorf("mkdir %s failed: %s", packagePath, output)
|
||||
return "", errors.Wrapf(err, "mkdir %s failed: %s", packagePath, output)
|
||||
}
|
||||
}
|
||||
{
|
||||
// download disk files
|
||||
for i, backupId := range backupInfo.BackupIds {
|
||||
packageDiskPath := path.Join(packagePath, fmt.Sprintf("%s_%d", PackageDiskFilename, i))
|
||||
err := backupStorage.RestoreBackupTo(ctx, packageDiskPath, backupId)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "RestoreBackupTo %s %s", backupId, packageDiskPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
// save snapshot metadata
|
||||
packageMetadataPath := path.Join(packagePath, PackageMetadataFilename)
|
||||
err = ioutil.WriteFile(packageMetadataPath, []byte(jsonutils.Marshal(backupInfo.Metadata).PrettyString()), 0644)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "unable to write to %s", packageMetadataPath)
|
||||
}
|
||||
}
|
||||
tmpPkgFilename := path.Join(backupTmpDir, backupInfo.PackageName+".tar")
|
||||
{
|
||||
// tar
|
||||
if output, err := procutils.NewRemoteCommandAsFarAsPossible("tar", "-cf", tmpPkgFilename, "-C", backupTmpDir, backupInfo.PackageName).Output(); err != nil {
|
||||
log.Errorf("unable to 'tar -cf %s -C %s %s': %s", tmpPkgFilename, backupTmpDir, backupInfo.PackageName, output)
|
||||
return "", errors.Wrap(err, "unable to tar")
|
||||
}
|
||||
}
|
||||
|
||||
var finalPackageName string
|
||||
tried := 0
|
||||
for {
|
||||
var finalPackageFileName string
|
||||
if tried == 0 {
|
||||
finalPackageFileName = fmt.Sprintf("%s.tar", backupInfo.PackageName)
|
||||
} else {
|
||||
finalPackageFileName = fmt.Sprintf("%s-%d.tar", backupInfo.PackageName, tried)
|
||||
}
|
||||
exists, err := backupStorage.IsBackupInstanceExists(finalPackageFileName)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "IsBackupInstanceExists")
|
||||
}
|
||||
if exists {
|
||||
tried++
|
||||
} else {
|
||||
err := backupStorage.SaveBackupInstanceFrom(ctx, tmpPkgFilename, finalPackageFileName)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "SaveBackupInstanceFrom")
|
||||
}
|
||||
finalPackageName = finalPackageFileName
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return finalPackageName, nil
|
||||
}
|
||||
|
||||
func DoInstanceUnpackBackup(ctx context.Context, backupInfo SStorageUnpackInstanceBackup) ([]string, *api.InstanceBackupPackMetadata, error) {
|
||||
backupTmpDir, err := ensureBackupDir()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "ensureBackupDir")
|
||||
}
|
||||
defer cleanupDirOrFile(backupTmpDir)
|
||||
|
||||
packageName := backupInfo.PackageName
|
||||
metadataOnly := false
|
||||
if backupInfo.MetadataOnly != nil && *backupInfo.MetadataOnly {
|
||||
metadataOnly = true
|
||||
}
|
||||
|
||||
backupStorage, err := backupstorage.GetBackupStorage(backupInfo.BackupStorageId, backupInfo.BackupStorageAccessInfo)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "GetBackupStorage")
|
||||
}
|
||||
|
||||
packageFilename := path.Join(backupTmpDir, packageName+".tar")
|
||||
err = backupStorage.RestoreBackupInstanceTo(ctx, packageFilename, backupInfo.PackageName)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "RestoreBackupInstanceTo")
|
||||
}
|
||||
|
||||
// untar to temp dir
|
||||
packagePath := path.Join(backupTmpDir, packageName)
|
||||
log.Infof("unpack to %s", packagePath)
|
||||
untarArgs := []string{
|
||||
"-xf", packageFilename, "-C", backupTmpDir,
|
||||
}
|
||||
if metadataOnly {
|
||||
untarArgs = append(untarArgs, fmt.Sprintf("%s/metadata", packageName))
|
||||
} else {
|
||||
untarArgs = append(untarArgs, packageName)
|
||||
}
|
||||
if output, err := procutils.NewCommand("tar", untarArgs...).Output(); err != nil {
|
||||
log.Errorf("unable to 'tar -xf %s -C %s %s': %s", packageFilename, backupTmpDir, packageName, output)
|
||||
return nil, nil, errors.Wrap(err, "unable to untar")
|
||||
}
|
||||
|
||||
// unpack metadata
|
||||
packageMetadataPath := path.Join(packagePath, PackageMetadataFilename)
|
||||
metadataBytes, err := ioutil.ReadFile(packageMetadataPath)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "unable to read metadata file")
|
||||
}
|
||||
metadataJson, err := jsonutils.Parse(metadataBytes)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "unable to parse string to json")
|
||||
}
|
||||
metadata := &api.InstanceBackupPackMetadata{}
|
||||
err = metadataJson.Unmarshal(metadata)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "unmarshal backup metadata")
|
||||
}
|
||||
|
||||
// copy disk files only if !metadataOnly
|
||||
backupIds := make([]string, len(metadata.DiskMetadatas))
|
||||
if !metadataOnly {
|
||||
for i := 0; i < len(metadata.DiskMetadatas); i++ {
|
||||
backupId := db.DefaultUUIDGenerator()
|
||||
backupIds[i] = backupId
|
||||
packageDiskPath := path.Join(packagePath, fmt.Sprintf("%s_%d", PackageDiskFilename, i))
|
||||
err := backupStorage.SaveBackupFrom(ctx, packageDiskPath, backupId)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "SaveBackupFrom %s %s", packageDiskPath, backupId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return backupIds, metadata, nil
|
||||
}
|
||||
@@ -16,46 +16,82 @@ package backupstorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/util/qemuimgfmt"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/pkg/errors"
|
||||
)
|
||||
|
||||
type IBackupStorageFactory interface {
|
||||
NewBackupStore(storeId string, backupStorageAccessInfo *jsonutils.JSONDict) (IBackupStorage, error)
|
||||
}
|
||||
|
||||
type IBackupStorage interface {
|
||||
CopyBackupFrom(srcFilename string, bakcupId string) error
|
||||
CopyBackupTo(targetFilename string, backupId string) error
|
||||
RemoveBackup(backupId string) error
|
||||
IsExists(backupId string) (bool, error)
|
||||
ConvertTo(destPath string, format qemuimgfmt.TImageFormat, backupId string) error
|
||||
ConvertFrom(srcPath string, format qemuimgfmt.TImageFormat, backupId string) (int, error)
|
||||
InstancePack(ctx context.Context, packageName string, backupIds []string, metadata *api.InstanceBackupPackMetadata) (string, error)
|
||||
InstanceUnpack(ctx context.Context, packageName string, metadataOnly bool) ([]string, *api.InstanceBackupPackMetadata, error)
|
||||
// 从指定路径拷贝磁盘文件到备份存储
|
||||
SaveBackupFrom(ctx context.Context, srcFilename string, bakcupId string) error
|
||||
// 将备份backupId对应的备份文件拷贝到指定的文件路径
|
||||
RestoreBackupTo(ctx context.Context, targetFilename string, backupId string) error
|
||||
// 删除备份
|
||||
RemoveBackup(ctx context.Context, backupId string) error
|
||||
// 备份是否存在
|
||||
IsBackupExists(backupId string) (bool, error)
|
||||
|
||||
// 从指定路径拷贝主机备份文件到备份存储
|
||||
SaveBackupInstanceFrom(ctx context.Context, srcFilename string, bakcupInstanceId string) error
|
||||
// 将备份backupId对应的备份文件拷贝到指定的文件路径
|
||||
RestoreBackupInstanceTo(ctx context.Context, targetFilename string, backupInstanceId string) error
|
||||
// 删除备份
|
||||
RemoveBackupInstance(ctx context.Context, backupInstanceId string) error
|
||||
// 备份是否存在
|
||||
IsBackupInstanceExists(backupInstanceId string) (bool, error)
|
||||
|
||||
// ConvertTo(destPath string, format qemuimgfmt.TImageFormat, backupId string) error
|
||||
// ConvertFrom(srcPath string, format qemuimgfmt.TImageFormat, backupId string) (int, error)
|
||||
// InstancePack(ctx context.Context, packageName string, backupIds []string, metadata *api.InstanceBackupPackMetadata) (string, error)
|
||||
// InstanceUnpack(ctx context.Context, packageName string, metadataOnly bool) ([]string, *api.InstanceBackupPackMetadata, error)
|
||||
|
||||
// 存储是否在线
|
||||
IsOnline() (bool, string, error)
|
||||
}
|
||||
|
||||
var backupStoragePool *sync.Map = &sync.Map{}
|
||||
var factories []IBackupStorageFactory
|
||||
var backupStoragePool map[string]IBackupStorage
|
||||
var backupStorageLock *sync.Mutex
|
||||
|
||||
func NewBackupStorage(backupStroageId string, backupStorageAccessInfo *jsonutils.JSONDict) (IBackupStorage, error) {
|
||||
nfsHost, err := backupStorageAccessInfo.GetString("nfs_host")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("need nfs_host in backup_storage_access_info")
|
||||
func init() {
|
||||
backupStorageLock = &sync.Mutex{}
|
||||
backupStoragePool = make(map[string]IBackupStorage)
|
||||
}
|
||||
|
||||
func RegisterFactory(factory IBackupStorageFactory) {
|
||||
factories = append(factories, factory)
|
||||
}
|
||||
|
||||
func newBackupStorage(backupStroageId string, backupStorageAccessInfo *jsonutils.JSONDict) (IBackupStorage, error) {
|
||||
errs := make([]error, 0)
|
||||
for _, factory := range factories {
|
||||
store, err := factory.NewBackupStore(backupStroageId, backupStorageAccessInfo)
|
||||
if err == nil {
|
||||
return store, nil
|
||||
} else {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
nfsSharedDir, err := backupStorageAccessInfo.GetString("nfs_shared_dir")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("need nfs_shared_dir in backup_storage_access_info")
|
||||
}
|
||||
return NewNFSBackupStorage(backupStroageId, nfsHost, nfsSharedDir), nil
|
||||
return nil, errors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func GetBackupStorage(backupStroageId string, backupStorageAccessInfo *jsonutils.JSONDict) (IBackupStorage, error) {
|
||||
bs, err := NewBackupStorage(backupStroageId, backupStorageAccessInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
backupStorageLock.Lock()
|
||||
defer backupStorageLock.Unlock()
|
||||
|
||||
if ibs, ok := backupStoragePool[backupStroageId]; !ok {
|
||||
bs, err := newBackupStorage(backupStroageId, backupStorageAccessInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "newBackupStorage")
|
||||
}
|
||||
backupStoragePool[backupStroageId] = bs
|
||||
return bs, nil
|
||||
} else {
|
||||
return ibs, nil
|
||||
}
|
||||
ibs, _ := backupStoragePool.LoadOrStore(backupStroageId, bs)
|
||||
return ibs.(IBackupStorage), nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// 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 nfs // import "yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage/nfs"
|
||||
@@ -0,0 +1,45 @@
|
||||
// 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 nfs
|
||||
|
||||
import (
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
)
|
||||
|
||||
type sNfsBackupStorageFactory struct{}
|
||||
|
||||
func (factory *sNfsBackupStorageFactory) NewBackupStore(backupStroageId string, backupStorageAccessInfo *jsonutils.JSONDict) (backupstorage.IBackupStorage, error) {
|
||||
accessInfo := api.SBackupStorageAccessInfo{}
|
||||
err := backupStorageAccessInfo.Unmarshal(&accessInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Unmarshal access info")
|
||||
}
|
||||
if len(accessInfo.NfsHost) == 0 {
|
||||
return nil, errors.Wrap(httperrors.ErrInputParameter, "need nfs_host in backup_storage_access_info")
|
||||
}
|
||||
if len(accessInfo.NfsSharedDir) == 0 {
|
||||
return nil, errors.Wrap(httperrors.ErrInputParameter, "need nfs_shared_dir in backup_storage_access_info")
|
||||
}
|
||||
return newNFSBackupStorage(backupStroageId, accessInfo.NfsHost, accessInfo.NfsSharedDir), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
backupstorage.RegisterFactory(&sNfsBackupStorageFactory{})
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
// 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 nfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/hostman/options"
|
||||
"yunion.io/x/onecloud/pkg/util/fileutils2"
|
||||
"yunion.io/x/onecloud/pkg/util/procutils"
|
||||
)
|
||||
|
||||
var ErrorBackupStorageOffline error = errors.Error(api.BackupStorageOffline)
|
||||
|
||||
type SNFSBackupStorage struct {
|
||||
BackupStorageId string
|
||||
Path string
|
||||
NfsHost string
|
||||
NfsSharedDir string
|
||||
lock *sync.Mutex
|
||||
userNumber int
|
||||
}
|
||||
|
||||
func newNFSBackupStorage(backupStorageId, nfsHost, nfsSharedDir string) *SNFSBackupStorage {
|
||||
return &SNFSBackupStorage{
|
||||
BackupStorageId: backupStorageId,
|
||||
NfsHost: nfsHost,
|
||||
NfsSharedDir: nfsSharedDir,
|
||||
Path: path.Join(options.HostOptions.LocalBackupStoragePath, backupStorageId),
|
||||
lock: &sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) getBackupDir() string {
|
||||
return path.Join(s.Path, "backups")
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) getBackupDiskPath(backupId string) string {
|
||||
return path.Join(s.getBackupDir(), backupId)
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) getPackageDir() string {
|
||||
return path.Join(s.Path, "backuppacks")
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) getBackupInstancePath(backupInstanceId string) string {
|
||||
return path.Join(s.getPackageDir(), backupInstanceId)
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) checkAndMount() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
if !fileutils2.Exists(s.Path) {
|
||||
output, err := procutils.NewCommand("mkdir", "-p", s.Path).Output()
|
||||
if err != nil {
|
||||
log.Errorf("mkdir %s failed: %s", s.Path, output)
|
||||
return errors.Wrapf(err, "mkdir %s failed: %s", s.Path, output)
|
||||
}
|
||||
}
|
||||
if err := procutils.NewRemoteCommandAsFarAsPossible("mountpoint", s.Path).Run(); err == nil {
|
||||
s.userNumber++
|
||||
return nil
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
err := procutils.NewRemoteCommandContextAsFarAsPossible(ctx,
|
||||
"mount", "-t", "nfs", fmt.Sprintf("%s:%s", s.NfsHost, s.NfsSharedDir), s.Path).Run()
|
||||
if err != nil {
|
||||
return errors.Wrap(ErrorBackupStorageOffline, err.Error())
|
||||
}
|
||||
backupDir := s.getBackupDir()
|
||||
if !fileutils2.Exists(backupDir) {
|
||||
output, err := procutils.NewCommand("mkdir", "-p", backupDir).Output()
|
||||
if err != nil {
|
||||
log.Errorf("mkdir %s failed: %s", backupDir, output)
|
||||
return errors.Wrapf(err, "mkdir %s failed: %s", backupDir, output)
|
||||
}
|
||||
}
|
||||
packageDir := s.getPackageDir()
|
||||
if !fileutils2.Exists(packageDir) {
|
||||
output, err := procutils.NewCommand("mkdir", "-p", packageDir).Output()
|
||||
if err != nil {
|
||||
log.Errorf("mkdir %s failed: %s", packageDir, output)
|
||||
return errors.Wrapf(err, "mkdir %s failed: %s", packageDir, output)
|
||||
}
|
||||
}
|
||||
s.userNumber++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) unMount() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
s.userNumber--
|
||||
if s.userNumber > 0 {
|
||||
return nil
|
||||
}
|
||||
out, err := procutils.NewRemoteCommandAsFarAsPossible("umount", s.Path).Output()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "umount %s failed %s", s.Path, out)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) SaveBackupFrom(ctx context.Context, srcFilename string, backupId string) error {
|
||||
return s.saveFile(ctx, srcFilename, backupId, s.getBackupDiskPath)
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) SaveBackupInstanceFrom(ctx context.Context, srcFilename string, backupId string) error {
|
||||
return s.saveFile(ctx, srcFilename, backupId, s.getBackupDiskPath)
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) saveFile(ctx context.Context, srcFilename string, id string, getPathFunc func(string) string) error {
|
||||
err := s.checkAndMount()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to checkAndMount")
|
||||
}
|
||||
defer s.unMount()
|
||||
|
||||
targetFilename := getPathFunc(id)
|
||||
if output, err := procutils.NewCommand("cp", srcFilename, targetFilename).Output(); err != nil {
|
||||
log.Errorf("unable to cp %s to %s: %s", srcFilename, targetFilename, output)
|
||||
return errors.Wrapf(err, "cp %s to %s failed and output is %q", srcFilename, targetFilename, output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) RestoreBackupTo(ctx context.Context, targetFilename string, backupId string) error {
|
||||
return s.restoreFile(ctx, targetFilename, backupId, s.getBackupDiskPath)
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) RestoreBackupInstanceTo(ctx context.Context, targetFilename string, backupId string) error {
|
||||
return s.restoreFile(ctx, targetFilename, backupId, s.getBackupInstancePath)
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) restoreFile(ctx context.Context, targetFilename string, id string, getPathFunc func(string) string) error {
|
||||
err := s.checkAndMount()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to checkAndMount")
|
||||
}
|
||||
defer s.unMount()
|
||||
|
||||
srcFilename := getPathFunc(id)
|
||||
if output, err := procutils.NewCommand("cp", srcFilename, targetFilename).Output(); err != nil {
|
||||
log.Errorf("unable to cp %s to %s: %s", srcFilename, targetFilename, output)
|
||||
return errors.Wrapf(err, "cp %s to %s failed and output is %q", srcFilename, targetFilename, output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) RemoveBackup(ctx context.Context, backupId string) error {
|
||||
return s.removeFile(ctx, backupId, s.getBackupDiskPath)
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) RemoveBackupInstance(ctx context.Context, backupId string) error {
|
||||
return s.removeFile(ctx, backupId, s.getBackupInstancePath)
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) removeFile(ctx context.Context, id string, getPathFunc func(id string) string) error {
|
||||
err := s.checkAndMount()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to checkAndMount")
|
||||
}
|
||||
defer s.unMount()
|
||||
|
||||
filename := getPathFunc(id)
|
||||
if !fileutils2.Exists(filename) {
|
||||
return nil
|
||||
}
|
||||
if output, err := procutils.NewCommand("rm", filename).Output(); err != nil {
|
||||
log.Errorf("unable to rm %s: %s", filename, output)
|
||||
return errors.Wrapf(err, "rm %s failed and output is %q", filename, output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) IsBackupExists(backupId string) (bool, error) {
|
||||
return s.isFileExists(backupId, s.getBackupDiskPath)
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) IsBackupInstanceExists(backupId string) (bool, error) {
|
||||
return s.isFileExists(backupId, s.getBackupInstancePath)
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) isFileExists(id string, getPathFunc func(id string) string) (bool, error) {
|
||||
err := s.checkAndMount()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "unable to checkAndMount")
|
||||
}
|
||||
defer s.unMount()
|
||||
|
||||
filename := getPathFunc(id)
|
||||
return fileutils2.Exists(filename), nil
|
||||
}
|
||||
|
||||
func (s *SNFSBackupStorage) IsOnline() (bool, string, error) {
|
||||
err := s.checkAndMount()
|
||||
if errors.Cause(err) == ErrorBackupStorageOffline {
|
||||
return false, err.Error(), nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
s.unMount()
|
||||
return true, "", nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// 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 object // import "yunion.io/x/onecloud/pkg/hostman/storageman/object"
|
||||
@@ -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 object
|
||||
|
||||
import (
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
)
|
||||
|
||||
type sObjectBackupStorageFactory struct{}
|
||||
|
||||
func (factory *sObjectBackupStorageFactory) NewBackupStore(backupStroageId string, backupStorageAccessInfo *jsonutils.JSONDict) (backupstorage.IBackupStorage, error) {
|
||||
accessInfo := api.SBackupStorageAccessInfo{}
|
||||
err := backupStorageAccessInfo.Unmarshal(&accessInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Unmarshal access info")
|
||||
}
|
||||
if len(accessInfo.ObjectBucketUrl) == 0 {
|
||||
return nil, errors.Wrap(httperrors.ErrInputParameter, "need object_bucket_url in backup_storage_access_info")
|
||||
}
|
||||
if len(accessInfo.ObjectAccessKey) == 0 {
|
||||
return nil, errors.Wrap(httperrors.ErrInputParameter, "need object_access_key in backup_storage_access_info")
|
||||
}
|
||||
if len(accessInfo.ObjectSecret) == 0 {
|
||||
return nil, errors.Wrap(httperrors.ErrInputParameter, "need object_secret in backup_storage_access_info")
|
||||
}
|
||||
return newObjectBackupStorage(backupStroageId, accessInfo.ObjectBucketUrl, accessInfo.ObjectAccessKey, accessInfo.ObjectSecret)
|
||||
}
|
||||
|
||||
func init() {
|
||||
backupstorage.RegisterFactory(&sObjectBackupStorageFactory{})
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// 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 object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/cloudmux/pkg/cloudprovider"
|
||||
"yunion.io/x/cloudmux/pkg/multicloud/objectstore"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/streamutils"
|
||||
)
|
||||
|
||||
type SObjectBackupStorage struct {
|
||||
BackupStorageId string
|
||||
|
||||
endpoint string
|
||||
bucket string
|
||||
accessKey string
|
||||
secret string
|
||||
|
||||
store *objectstore.SObjectStoreClient
|
||||
}
|
||||
|
||||
func newObjectBackupStorage(backupStorageId, bucketUrl, accessKey, secret string) (*SObjectBackupStorage, error) {
|
||||
bucket, endpoint, err := parseBucketUrl(bucketUrl)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "parseBucketUrl %s", bucketUrl)
|
||||
}
|
||||
cfg := objectstore.NewObjectStoreClientConfig(endpoint, accessKey, secret)
|
||||
store, err := objectstore.NewObjectStoreClient(cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "NewObjectStoreClient")
|
||||
}
|
||||
return &SObjectBackupStorage{
|
||||
BackupStorageId: backupStorageId,
|
||||
|
||||
endpoint: endpoint,
|
||||
bucket: bucket,
|
||||
accessKey: accessKey,
|
||||
secret: secret,
|
||||
|
||||
store: store,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseBucketUrl(bucketUrl string) (string, string, error) {
|
||||
bu, err := url.Parse(bucketUrl)
|
||||
if err != nil {
|
||||
return "", "", errors.Wrapf(err, "ur.Parse %s", bucketUrl)
|
||||
}
|
||||
for len(bu.Path) > 0 && bu.Path[0] == '/' {
|
||||
bu.Path = bu.Path[1:]
|
||||
}
|
||||
if len(bu.Path) > 0 {
|
||||
bucket := strings.TrimRight(bu.Path, "/")
|
||||
bu.Path = ""
|
||||
return bucket, fmt.Sprintf("%s://%s", bu.Scheme, bu.Host), nil
|
||||
} else {
|
||||
parts := strings.Split(bu.Host, ".")
|
||||
if len(parts) < 3 {
|
||||
return "", "", errors.Wrapf(errors.ErrInvalidFormat, "host %s should have at least 3 segments", bu.Host)
|
||||
}
|
||||
return parts[0], fmt.Sprintf("%s//%s", bu.Scheme, bu.Host), nil
|
||||
}
|
||||
}
|
||||
|
||||
const backupPathPrefix = "backups"
|
||||
const backupInstancePathPrefix = "backuppacks"
|
||||
|
||||
func (s *SObjectBackupStorage) getBackupKey(backupId string) string {
|
||||
return fmt.Sprintf("%s/%s", backupPathPrefix, backupId)
|
||||
}
|
||||
|
||||
func (s *SObjectBackupStorage) getBackupInstanceKey(backupInstancePackName string) string {
|
||||
return fmt.Sprintf("%s/%s", backupInstancePathPrefix, backupInstancePackName)
|
||||
}
|
||||
|
||||
func (s *SObjectBackupStorage) getBucket() (cloudprovider.ICloudBucket, error) {
|
||||
bucket, err := s.store.GetIRegion().GetIBucketByName(s.bucket)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "IBucketExist")
|
||||
}
|
||||
return bucket, nil
|
||||
}
|
||||
|
||||
func (s *SObjectBackupStorage) SaveBackupFrom(ctx context.Context, srcFilename string, backupId string) error {
|
||||
return s.saveObject(ctx, srcFilename, backupId, s.getBackupKey)
|
||||
}
|
||||
|
||||
func (s *SObjectBackupStorage) SaveBackupInstanceFrom(ctx context.Context, srcFilename string, backupId string) error {
|
||||
return s.saveObject(ctx, srcFilename, backupId, s.getBackupInstanceKey)
|
||||
}
|
||||
|
||||
func (s *SObjectBackupStorage) saveObject(ctx context.Context, srcFilename string, id string, getKeyFunc func(string) string) error {
|
||||
bucket, err := s.getBucket()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getBucket")
|
||||
}
|
||||
fileInfo, err := os.Stat(srcFilename)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "stat %s", srcFilename)
|
||||
}
|
||||
file, err := os.Open(srcFilename)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Open %s", srcFilename)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
err = cloudprovider.UploadObject(ctx, bucket, getKeyFunc(id), 200*1024*1024, file, fileInfo.Size(), cloudprovider.ACLPrivate, "", nil, false)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "UploadObject %s %s", srcFilename, getKeyFunc(id))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SObjectBackupStorage) RestoreBackupTo(ctx context.Context, targetFilename string, backupId string) error {
|
||||
return s.restoreObject(ctx, targetFilename, backupId, s.getBackupKey)
|
||||
}
|
||||
|
||||
func (s *SObjectBackupStorage) RestoreBackupInstanceTo(ctx context.Context, targetFilename string, backupId string) error {
|
||||
return s.restoreObject(ctx, targetFilename, backupId, s.getBackupInstanceKey)
|
||||
}
|
||||
|
||||
func (s *SObjectBackupStorage) restoreObject(ctx context.Context, targetFilename string, id string, getKeyFunc func(string) string) error {
|
||||
bucket, err := s.getBucket()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getBucket")
|
||||
}
|
||||
reader, err := bucket.GetObject(ctx, getKeyFunc(id), nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "GetObject")
|
||||
}
|
||||
file, err := os.OpenFile(targetFilename, os.O_CREATE|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "OpenFile %s", targetFilename)
|
||||
}
|
||||
defer file.Close()
|
||||
_, err = streamutils.StreamPipe(reader, file, false, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "StreamPipe")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SObjectBackupStorage) RemoveBackup(ctx context.Context, backupId string) error {
|
||||
return s.removeObject(ctx, backupId, s.getBackupKey)
|
||||
}
|
||||
|
||||
func (s *SObjectBackupStorage) RemoveBackupInstance(ctx context.Context, backupId string) error {
|
||||
return s.removeObject(ctx, backupId, s.getBackupInstanceKey)
|
||||
}
|
||||
|
||||
func (s *SObjectBackupStorage) removeObject(ctx context.Context, id string, getKeyFunc func(string) string) error {
|
||||
bucket, err := s.getBucket()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getBucket")
|
||||
}
|
||||
err = bucket.DeleteObject(ctx, getKeyFunc(id))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "DeleteObject")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SObjectBackupStorage) IsBackupExists(backupId string) (bool, error) {
|
||||
return s.isObjectExists(backupId, s.getBackupKey)
|
||||
}
|
||||
|
||||
func (s *SObjectBackupStorage) IsBackupInstanceExists(backupId string) (bool, error) {
|
||||
return s.isObjectExists(backupId, s.getBackupInstanceKey)
|
||||
}
|
||||
|
||||
func (s *SObjectBackupStorage) isObjectExists(id string, getKeyFunc func(string) string) (bool, error) {
|
||||
bucket, err := s.getBucket()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "getBucket")
|
||||
}
|
||||
_, err = cloudprovider.GetIObject(bucket, getKeyFunc(id))
|
||||
if err != nil {
|
||||
if errors.Cause(err) == errors.ErrNotFound {
|
||||
return false, nil
|
||||
}
|
||||
return false, errors.Wrap(err, "GetIObject")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s *SObjectBackupStorage) IsOnline() (bool, string, error) {
|
||||
exist, err := s.store.GetIRegion().IBucketExist(s.bucket)
|
||||
if err != nil {
|
||||
return false, "", errors.Wrap(err, "IBucketExist")
|
||||
}
|
||||
return exist, "", nil
|
||||
}
|
||||
@@ -38,12 +38,14 @@ type IDisk interface {
|
||||
Probe() error
|
||||
GetPath() string
|
||||
GetFormat() (string, error)
|
||||
GetSnapshotDir() string
|
||||
GetDiskDesc() jsonutils.JSONObject
|
||||
GetDiskSetupScripts(idx int) string
|
||||
GetSnapshotLocation() string
|
||||
OnRebuildRoot(ctx context.Context, params api.DiskAllocateInput) error
|
||||
|
||||
GetSnapshotDir() string
|
||||
DoDeleteSnapshot(snapshotId string) error
|
||||
GetSnapshotLocation() string
|
||||
|
||||
GetStorage() IStorage
|
||||
|
||||
DeleteAllSnapshot(skipRecycle bool) error
|
||||
@@ -69,7 +71,7 @@ type IDisk interface {
|
||||
DeployGuestFs(diskInfo *deployapi.DiskInfo, guestDesc *desc.SGuestDesc,
|
||||
deployInfo *deployapi.DeployInfo) (jsonutils.JSONObject, error)
|
||||
|
||||
GetBackupDir() string
|
||||
// GetBackupDir() string
|
||||
DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error)
|
||||
|
||||
IsFile() bool
|
||||
@@ -212,10 +214,6 @@ func (d *SBaseDisk) DiskSnapshot(ctx context.Context, params interface{}) (jsonu
|
||||
return nil, fmt.Errorf("Not implement disk.DiskSnapshot")
|
||||
}
|
||||
|
||||
func (d *SBaseDisk) DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
|
||||
return nil, fmt.Errorf("Not implement disk.DiskDeleteSnapshot")
|
||||
}
|
||||
|
||||
func (d *SBaseDisk) DiskDeleteSnapshot(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
|
||||
return nil, fmt.Errorf("Not implement disk.DiskDeleteSnapshot")
|
||||
}
|
||||
|
||||
@@ -27,12 +27,10 @@ import (
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/appctx"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/qemuimgfmt"
|
||||
"yunion.io/x/pkg/utils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
|
||||
deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
|
||||
"yunion.io/x/onecloud/pkg/hostman/hostdeployer/deployclient"
|
||||
"yunion.io/x/onecloud/pkg/hostman/hostutils"
|
||||
@@ -40,7 +38,6 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/hostman/storageman/remotefile"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
identity_modules "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
|
||||
"yunion.io/x/onecloud/pkg/util/fileutils2"
|
||||
"yunion.io/x/onecloud/pkg/util/fuseutils"
|
||||
"yunion.io/x/onecloud/pkg/util/procutils"
|
||||
@@ -48,6 +45,8 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/util/seclib2"
|
||||
)
|
||||
|
||||
var _ IDisk = (*SLocalDisk)(nil)
|
||||
|
||||
var _ALTER_SUFFIX_ = ".alter"
|
||||
|
||||
type SLocalDisk struct {
|
||||
@@ -93,10 +92,6 @@ func (d *SLocalDisk) GetSnapshotDir() string {
|
||||
return path.Join(d.Storage.GetSnapshotDir(), d.Id+options.HostOptions.SnapshotDirSuffix)
|
||||
}
|
||||
|
||||
func (d *SLocalDisk) GetBackupDir() string {
|
||||
return d.Storage.GetBackupDir()
|
||||
}
|
||||
|
||||
func (d *SLocalDisk) GetSnapshotLocation() string {
|
||||
return d.GetSnapshotDir()
|
||||
}
|
||||
@@ -408,52 +403,18 @@ func (d *SLocalDisk) PostCreateFromImageFuse() {
|
||||
}
|
||||
|
||||
func (d *SLocalDisk) DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
|
||||
diskBackup := params.(*SDiskBakcup)
|
||||
|
||||
encKey := ""
|
||||
if len(diskBackup.EncryptKeyId) > 0 {
|
||||
session := auth.GetSession(ctx, diskBackup.UserCred, consts.GetRegion())
|
||||
secKey, err := identity_modules.Credentials.GetEncryptKey(session, diskBackup.EncryptKeyId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetEncryptKey")
|
||||
}
|
||||
encKey = secKey.Key
|
||||
}
|
||||
diskBackup := params.(*SDiskBackup)
|
||||
|
||||
snapshotDir := d.GetSnapshotDir()
|
||||
snapshotPath := path.Join(snapshotDir, diskBackup.SnapshotId)
|
||||
backupDir := d.GetBackupDir()
|
||||
if !fileutils2.Exists(backupDir) {
|
||||
output, err := procutils.NewCommand("mkdir", "-p", backupDir).Output()
|
||||
if err != nil {
|
||||
log.Errorf("mkdir %s failed: %s", backupDir, output)
|
||||
return nil, errors.Wrapf(err, "mkdir %s failed: %s", backupDir, output)
|
||||
}
|
||||
}
|
||||
backupPath := path.Join(backupDir, diskBackup.BackupId)
|
||||
img, err := qemuimg.NewQemuImage(snapshotPath)
|
||||
|
||||
size, err := doBackupDisk(ctx, snapshotPath, diskBackup)
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
procutils.NewCommand("mv", "-f", backupPath, d.getPath()).Run()
|
||||
return nil, err
|
||||
}
|
||||
if len(encKey) > 0 {
|
||||
img.SetPassword(encKey)
|
||||
}
|
||||
newImage, err := img.Clone(backupPath, qemuimgfmt.QCOW2, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to backup snapshot")
|
||||
}
|
||||
_, err = d.Storage.StorageBackup(ctx, &SStorageBackup{
|
||||
BackupId: diskBackup.BackupId,
|
||||
BackupStorageId: diskBackup.BackupStorageId,
|
||||
BackupStorageAccessInfo: diskBackup.BackupStorageAccessInfo,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to SStorageBackup")
|
||||
return nil, errors.Wrap(err, "doBackupDisk")
|
||||
}
|
||||
|
||||
data := jsonutils.NewDict()
|
||||
data.Set("size_mb", jsonutils.NewInt(int64(newImage.GetActualSizeMB())))
|
||||
data.Set("size_mb", jsonutils.NewInt(int64(size)))
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@@ -712,8 +673,12 @@ func (d *SLocalDisk) PrepareMigrate(liveMigrate bool) ([]string, string, bool, e
|
||||
return nil, "", false, nil
|
||||
}
|
||||
|
||||
func (d *SLocalDisk) GetSnapshotPath(snapshotId string) string {
|
||||
return path.Join(d.GetSnapshotDir(), snapshotId)
|
||||
}
|
||||
|
||||
func (d *SLocalDisk) DoDeleteSnapshot(snapshotId string) error {
|
||||
snapshotPath := path.Join(d.GetSnapshotDir(), snapshotId)
|
||||
snapshotPath := d.GetSnapshotPath(snapshotId)
|
||||
return d.Storage.DeleteDiskfile(snapshotPath, false)
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/util/qemuimg"
|
||||
)
|
||||
|
||||
var _ IDisk = (*SLVMDisk)(nil)
|
||||
|
||||
type SLVMDisk struct {
|
||||
SBaseDisk
|
||||
}
|
||||
@@ -332,6 +334,10 @@ func (d *SLVMDisk) createFromTemplate(
|
||||
return d.GetDiskDesc(), nil
|
||||
}
|
||||
|
||||
func (d *SLVMDisk) DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
|
||||
return nil, errors.ErrNotImplemented
|
||||
}
|
||||
|
||||
func NewLVMDisk(storage IStorage, id string) *SLVMDisk {
|
||||
return &SLVMDisk{
|
||||
SBaseDisk: *NewBaseDisk(storage, id),
|
||||
|
||||
@@ -31,6 +31,8 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/util/qemuimg"
|
||||
)
|
||||
|
||||
var _ IDisk = (*SNasDisk)(nil)
|
||||
|
||||
type SNasDisk struct {
|
||||
SLocalDisk
|
||||
}
|
||||
|
||||
@@ -18,11 +18,14 @@ import (
|
||||
"context"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/qemuimgfmt"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
)
|
||||
|
||||
var _ IDisk = (*SNVMEDisk)(nil)
|
||||
|
||||
type SNVMEDisk struct {
|
||||
SBaseDisk
|
||||
}
|
||||
@@ -61,6 +64,10 @@ func (d *SNVMEDisk) Probe() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *SNVMEDisk) DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
|
||||
return nil, errors.ErrNotImplemented
|
||||
}
|
||||
|
||||
func NewNVMEDisk(storage IStorage, id string) *SNVMEDisk {
|
||||
return &SNVMEDisk{
|
||||
SBaseDisk: *NewBaseDisk(storage, id),
|
||||
|
||||
@@ -34,6 +34,8 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/util/seclib2"
|
||||
)
|
||||
|
||||
var _ IDisk = (*SRBDDisk)(nil)
|
||||
|
||||
type SRBDDisk struct {
|
||||
SBaseDisk
|
||||
}
|
||||
@@ -79,9 +81,8 @@ func (d *SRBDDisk) GetSnapshotDir() string {
|
||||
|
||||
func (d *SRBDDisk) GetDiskDesc() jsonutils.JSONObject {
|
||||
storage := d.Storage.(*SRbdStorage)
|
||||
storageConf := d.Storage.GetStorageConf()
|
||||
pool, _ := storageConf.GetString("pool")
|
||||
sizeMb, _ := storage.getImageSizeMb(pool, d.Id)
|
||||
|
||||
sizeMb, _ := storage.getImageSizeMb(d.Id)
|
||||
desc := map[string]interface{}{
|
||||
"disk_id": d.Id,
|
||||
"disk_format": "raw",
|
||||
@@ -102,9 +103,8 @@ func (d *SRBDDisk) DeleteAllSnapshot(skipRecycle bool) error {
|
||||
func (d *SRBDDisk) Delete(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
|
||||
p := params.(api.DiskDeleteInput)
|
||||
storage := d.Storage.(*SRbdStorage)
|
||||
storageConf := d.Storage.GetStorageConf()
|
||||
pool, _ := storageConf.GetString("pool")
|
||||
return nil, storage.deleteImage(pool, d.Id, p.SkipRecycle != nil && *p.SkipRecycle)
|
||||
|
||||
return nil, storage.deleteImage(d.Id, p.SkipRecycle != nil && *p.SkipRecycle)
|
||||
}
|
||||
|
||||
func (d *SRBDDisk) OnRebuildRoot(ctx context.Context, params api.DiskAllocateInput) error {
|
||||
@@ -124,10 +124,8 @@ func (d *SRBDDisk) Resize(ctx context.Context, params interface{}) (jsonutils.JS
|
||||
return nil, hostutils.ParamsError
|
||||
}
|
||||
storage := d.Storage.(*SRbdStorage)
|
||||
storageConf := d.Storage.GetStorageConf()
|
||||
pool, _ := storageConf.GetString("pool")
|
||||
sizeMb, _ := diskInfo.Int("size")
|
||||
if err := storage.resizeImage(pool, d.Id, uint64(sizeMb)); err != nil {
|
||||
if err := storage.resizeImage(d.Id, uint64(sizeMb)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -161,8 +159,7 @@ func (d *SRBDDisk) PrepareSaveToGlance(ctx context.Context, params interface{})
|
||||
|
||||
func (d *SRBDDisk) CleanupSnapshots(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
|
||||
storage := d.Storage.(*SRbdStorage)
|
||||
pool, _ := storage.StorageConf.GetString("pool")
|
||||
return nil, storage.deleteSnapshot(pool, d.Id, "")
|
||||
return nil, storage.deleteSnapshot(d.Id, "")
|
||||
}
|
||||
|
||||
func (d *SRBDDisk) PrepareMigrate(liveMigrate bool) ([]string, string, bool, error) {
|
||||
@@ -203,9 +200,9 @@ func (d *SRBDDisk) createFromTemplate(ctx context.Context, imageId, format strin
|
||||
defer imageCacheManager.ReleaseImage(ctx, imageId)
|
||||
|
||||
storage := d.Storage.(*SRbdStorage)
|
||||
destPool, _ := storage.StorageConf.GetString("pool")
|
||||
storage.deleteImage(destPool, d.Id, false) //重装系统时,需要删除以前的系统盘
|
||||
err = storage.cloneImage(ctx, imageCacheManager.GetPath(), imageCache.GetName(), destPool, d.Id)
|
||||
|
||||
storage.deleteImage(d.Id, false) //重装系统时,需要删除以前的系统盘
|
||||
err = storage.cloneImage(ctx, imageCacheManager.GetPath(), imageCache.GetName(), storage.Pool, d.Id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cloneImage(%s)", imageCache.GetName())
|
||||
}
|
||||
@@ -221,8 +218,8 @@ func (d *SRBDDisk) CreateRaw(ctx context.Context, sizeMb int, diskFromat string,
|
||||
return nil, errors.Wrap(httperrors.ErrNotSupported, "rbd not support encryptInfo")
|
||||
}
|
||||
storage := d.Storage.(*SRbdStorage)
|
||||
pool, _ := storage.StorageConf.GetString("pool")
|
||||
if err := storage.createImage(pool, diskId, uint64(sizeMb)); err != nil {
|
||||
|
||||
if err := storage.createImage(diskId, uint64(sizeMb)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -241,10 +238,10 @@ func (d *SRBDDisk) PostCreateFromImageFuse() {
|
||||
}
|
||||
|
||||
func (d *SRBDDisk) DiskBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
|
||||
diskBackup := params.(*SDiskBakcup)
|
||||
diskBackup := params.(*SDiskBackup)
|
||||
storage := d.Storage.(*SRbdStorage)
|
||||
pool, _ := storage.StorageConf.GetString("pool")
|
||||
sizeMb, err := storage.createBackup(pool, d.Id, diskBackup.SnapshotId, diskBackup.BackupId, diskBackup.BackupStorageId, diskBackup.BackupStorageAccessInfo)
|
||||
|
||||
sizeMb, err := storage.createBackup(ctx, d.Id, diskBackup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -255,14 +252,12 @@ func (d *SRBDDisk) DiskBackup(ctx context.Context, params interface{}) (jsonutil
|
||||
|
||||
func (d *SRBDDisk) CreateSnapshot(snapshotId string, encryptKey string, encFormat qemuimg.TEncryptFormat, encAlg seclib2.TSymEncAlg) error {
|
||||
storage := d.Storage.(*SRbdStorage)
|
||||
pool, _ := storage.StorageConf.GetString("pool")
|
||||
return storage.createSnapshot(pool, d.Id, snapshotId)
|
||||
return storage.createSnapshot(d.Id, snapshotId)
|
||||
}
|
||||
|
||||
func (d *SRBDDisk) DeleteSnapshot(snapshotId, convertSnapshot string, pendingDelete bool) error {
|
||||
storage := d.Storage.(*SRbdStorage)
|
||||
pool, _ := storage.StorageConf.GetString("pool")
|
||||
return storage.deleteSnapshot(pool, d.Id, snapshotId)
|
||||
return storage.deleteSnapshot(d.Id, snapshotId)
|
||||
}
|
||||
|
||||
func (d *SRBDDisk) DiskSnapshot(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
|
||||
@@ -298,8 +293,8 @@ func (d *SRBDDisk) ResetFromSnapshot(ctx context.Context, params interface{}) (j
|
||||
diskId = d.GetId()
|
||||
}
|
||||
storage := d.Storage.(*SRbdStorage)
|
||||
pool, _ := storage.StorageConf.GetString("pool")
|
||||
return nil, storage.resetDisk(pool, diskId, resetParams.SnapshotId)
|
||||
|
||||
return nil, storage.resetDisk(diskId, resetParams.SnapshotId)
|
||||
}
|
||||
|
||||
func (d *SRBDDisk) CreateFromRbdSnapshot(ctx context.Context, snapshot, srcDiskId, srcPool string) error {
|
||||
|
||||
@@ -379,7 +379,7 @@ func diskStorageBackupRecovery(ctx context.Context, storage storageman.IStorage,
|
||||
}
|
||||
|
||||
func diskBackup(ctx context.Context, userCred mcclient.TokenCredential, storage storageman.IStorage, diskId string, disk storageman.IDisk, body jsonutils.JSONObject) (interface{}, error) {
|
||||
backupInfo := &storageman.SDiskBakcup{}
|
||||
backupInfo := &storageman.SDiskBackup{}
|
||||
err := body.Unmarshal(backupInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "JsonUnmarshal")
|
||||
|
||||
@@ -102,9 +102,22 @@ func (r *SRbdImageCache) Release() {
|
||||
|
||||
func (r *SRbdImageCache) Remove(ctx context.Context) error {
|
||||
imageCacheManger := r.Manager.(*SRbdImageCacheManager)
|
||||
storage := imageCacheManger.storage.(*SRbdStorage)
|
||||
if err := storage.deleteImage(r.Manager.GetPath(), r.GetName(), false); err != nil {
|
||||
return err
|
||||
|
||||
cli, err := imageCacheManger.getCephClient()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getCephClient")
|
||||
}
|
||||
|
||||
img, err := cli.GetImage(r.GetName())
|
||||
if err != nil {
|
||||
if errors.Cause(err) == errors.ErrNotFound {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrapf(err, "GetImage")
|
||||
}
|
||||
err = img.Delete()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Delete")
|
||||
}
|
||||
|
||||
go func() {
|
||||
@@ -119,13 +132,30 @@ func (r *SRbdImageCache) Remove(ctx context.Context) error {
|
||||
|
||||
func (r *SRbdImageCache) GetDesc() *remotefile.SImageDesc {
|
||||
imageCacheManger := r.Manager.(*SRbdImageCacheManager)
|
||||
storage := imageCacheManger.storage.(*SRbdStorage)
|
||||
|
||||
size, _ := storage.getImageSizeMb(imageCacheManger.Pool, r.GetName())
|
||||
return &remotefile.SImageDesc{
|
||||
Size: int64(size),
|
||||
desc := &remotefile.SImageDesc{
|
||||
Size: -1,
|
||||
Name: r.imageName,
|
||||
}
|
||||
|
||||
cli, err := imageCacheManger.getCephClient()
|
||||
if err != nil {
|
||||
log.Errorf("getCephClient fail %s", err)
|
||||
return desc
|
||||
}
|
||||
img, err := cli.GetImage(r.GetName())
|
||||
if err != nil {
|
||||
log.Errorf("getName fail %s", err)
|
||||
return desc
|
||||
}
|
||||
info, err := img.GetInfo()
|
||||
if err != nil {
|
||||
log.Errorf("GetInfo fail %s", err)
|
||||
return desc
|
||||
}
|
||||
desc.Size = info.SizeByte / 1024 / 1024
|
||||
|
||||
return desc
|
||||
}
|
||||
|
||||
func (r *SRbdImageCache) GetImageId() string {
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
|
||||
"yunion.io/x/onecloud/pkg/hostman/hostutils"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/util/cephutils"
|
||||
)
|
||||
|
||||
type SRbdImageCacheManager struct {
|
||||
@@ -69,12 +70,23 @@ func init() {
|
||||
registerimageCacheManagerFactory(&SRbdImageCacheManagerFactory{})
|
||||
}
|
||||
|
||||
func (c *SRbdImageCacheManager) getCephClient() (*cephutils.CephClient, error) {
|
||||
storage := c.storage.(*SRbdStorage)
|
||||
return storage.getCephClient(c.Pool)
|
||||
}
|
||||
|
||||
func (c *SRbdImageCacheManager) loadCache(ctx context.Context) {
|
||||
lockman.LockRawObject(ctx, "RBD", "image-cache")
|
||||
defer lockman.ReleaseRawObject(ctx, "RBD", "image-cache")
|
||||
storage := c.storage.(*SRbdStorage)
|
||||
|
||||
images, err := storage.listImages(c.Pool)
|
||||
cli, err := c.getCephClient()
|
||||
if err != nil {
|
||||
log.Errorf("getCephClient %s fail %s", c.storage.GetStorageName(), err)
|
||||
return
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
images, err := cli.ListImages()
|
||||
if err != nil {
|
||||
log.Errorf("get storage %s images error; %v", c.storage.GetStorageName(), err)
|
||||
return
|
||||
|
||||
@@ -106,7 +106,11 @@ func (s *SLocalStorage) GetComposedName() string {
|
||||
}
|
||||
|
||||
func (s *SLocalStorage) CreateDiskFromBackup(ctx context.Context, disk IDisk, input *SDiskCreateByDiskinfo) error {
|
||||
info := input.DiskInfo
|
||||
err := doRestoreDisk(ctx, input.DiskInfo, disk.GetPath(), input.DiskInfo.Format)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "doRestoreDisk")
|
||||
}
|
||||
/*info := input.DiskInfo
|
||||
backupDir := s.GetBackupDir()
|
||||
if !fileutils2.Exists(backupDir) {
|
||||
output, err := procutils.NewCommand("mkdir", "-p", backupDir).Output()
|
||||
@@ -124,7 +128,7 @@ func (s *SLocalStorage) CreateDiskFromBackup(ctx context.Context, disk IDisk, in
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to storageBackupRecovery")
|
||||
}
|
||||
}
|
||||
}*/
|
||||
/*img, err := qemuimg.NewQemuImage(backupPath)
|
||||
if err != nil {
|
||||
log.Errorf("unable to new qemu image for %s: %s", backupPath, err.Error())
|
||||
@@ -134,7 +138,7 @@ func (s *SLocalStorage) CreateDiskFromBackup(ctx context.Context, disk IDisk, in
|
||||
img.SetPassword(info.EncryptInfo.Key)
|
||||
}
|
||||
_, err = img.Clone(disk.GetLvPath(), qemuimg.QCOW2, false)*/
|
||||
img, err := qemuimg.NewQemuImage(disk.GetPath())
|
||||
/*img, err := qemuimg.NewQemuImage(disk.GetPath())
|
||||
if err != nil {
|
||||
log.Errorf("NewQemuImage fail %s %s", disk.GetPath(), err)
|
||||
return errors.Wrapf(err, "unable to new qemu image for %s", disk.GetPath())
|
||||
@@ -153,18 +157,18 @@ func (s *SLocalStorage) CreateDiskFromBackup(ctx context.Context, disk IDisk, in
|
||||
if err != nil {
|
||||
log.Errorf("CreateQcow2 fail %s", err)
|
||||
return errors.Wrapf(err, "CreateQcow2 %s fail", backupPath)
|
||||
}
|
||||
}*/
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SLocalStorage) StorageBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
|
||||
/*func (s *SLocalStorage) StorageBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
|
||||
sbParams := params.(*SStorageBackup)
|
||||
backupStorage, err := backupstorage.GetBackupStorage(sbParams.BackupStorageId, sbParams.BackupStorageAccessInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
backupPath := path.Join(s.GetBackupDir(), sbParams.BackupId)
|
||||
err = backupStorage.CopyBackupFrom(backupPath, sbParams.BackupId)
|
||||
err = backupStorage.SaveBackupFrom(ctx, backupPath, sbParams.BackupId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -175,7 +179,7 @@ func (s *SLocalStorage) StorageBackup(ctx context.Context, params interface{}) (
|
||||
return nil, errors.Wrapf(err, "rm %s failed %s", backupPath, output)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
}*/
|
||||
|
||||
func (s *SLocalStorage) storageBackupRecovery(ctx context.Context, sbParams *SStorageBackup) (jsonutils.JSONObject, error) {
|
||||
backupStorage, err := backupstorage.GetBackupStorage(sbParams.BackupStorageId, sbParams.BackupStorageAccessInfo)
|
||||
@@ -183,7 +187,7 @@ func (s *SLocalStorage) storageBackupRecovery(ctx context.Context, sbParams *SSt
|
||||
return nil, err
|
||||
}
|
||||
backupPath := path.Join(s.GetBackupDir(), sbParams.BackupId)
|
||||
return nil, backupStorage.CopyBackupTo(backupPath, sbParams.BackupId)
|
||||
return nil, backupStorage.RestoreBackupTo(ctx, backupPath, sbParams.BackupId)
|
||||
}
|
||||
|
||||
func (s *SLocalStorage) StorageBackupRecovery(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
|
||||
@@ -518,6 +522,7 @@ func (s *SLocalStorage) SaveToGlance(ctx context.Context, params interface{}) (j
|
||||
if err := s.saveToGlance(ctx, imageId, imagePath, compress, format, encKey, encFormat, encAlg); err != nil {
|
||||
log.Errorf("Save to glance failed: %s", err)
|
||||
s.onSaveToGlanceFailed(ctx, imageId, err.Error())
|
||||
return nil, errors.Wrap(err, "saveToGlance")
|
||||
}
|
||||
|
||||
imagecacheManager := s.Manager.LocalStorageImagecacheManager
|
||||
|
||||
@@ -17,7 +17,9 @@ package storageman
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
@@ -33,13 +35,11 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/hostman/hostdeployer/deployclient"
|
||||
"yunion.io/x/onecloud/pkg/hostman/hostutils"
|
||||
"yunion.io/x/onecloud/pkg/hostman/options"
|
||||
"yunion.io/x/onecloud/pkg/hostman/storageman/backupstorage"
|
||||
modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules/image"
|
||||
"yunion.io/x/onecloud/pkg/util/cephutils"
|
||||
"yunion.io/x/onecloud/pkg/util/procutils"
|
||||
"yunion.io/x/onecloud/pkg/util/qemuimg"
|
||||
"yunion.io/x/onecloud/pkg/util/qemutils"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -95,16 +95,24 @@ func (s *SRbdStorage) GetSnapshotPathByIds(diskId, snapshotId string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) GetClient() (*cephutils.CephClient, error) {
|
||||
return cephutils.NewClient(s.MonHost, s.Key, s.Pool)
|
||||
func (s *SRbdStorage) getCephClient(pool string) (*cephutils.CephClient, error) {
|
||||
if pool == "" {
|
||||
pool = s.Pool
|
||||
}
|
||||
return cephutils.NewClient(s.MonHost, s.Key, pool)
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) getClient() (*cephutils.CephClient, error) {
|
||||
return s.getCephClient("")
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) IsSnapshotExist(diskId, snapshotId string) (bool, error) {
|
||||
client, err := s.GetClient()
|
||||
client, err := s.getClient()
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "GetClient")
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
img, err := client.GetImage(diskId)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "GetImage")
|
||||
@@ -149,18 +157,17 @@ func (s *SRbdStorage) getStorageConfString() string {
|
||||
return ":" + strings.Join(conf, ":")
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) listImages(pool string) ([]string, error) {
|
||||
client, err := s.GetClient()
|
||||
func (s *SRbdStorage) listImages() ([]string, error) {
|
||||
client, err := s.getClient()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "GetClient")
|
||||
}
|
||||
client.SetPool(pool)
|
||||
defer client.Close()
|
||||
return client.ListImages()
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) IsImageExist(name string) (bool, error) {
|
||||
images, err := s.listImages(s.Pool)
|
||||
images, err := s.listImages()
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "listImages")
|
||||
}
|
||||
@@ -170,13 +177,13 @@ func (s *SRbdStorage) IsImageExist(name string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) getImageSizeMb(pool string, name string) (uint64, error) {
|
||||
client, err := s.GetClient()
|
||||
func (s *SRbdStorage) getImageSizeMb(name string) (uint64, error) {
|
||||
client, err := s.getClient()
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "GetClient")
|
||||
}
|
||||
defer client.Close()
|
||||
client.SetPool(pool)
|
||||
|
||||
img, err := client.GetImage(name)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "GetImage")
|
||||
@@ -188,13 +195,13 @@ func (s *SRbdStorage) getImageSizeMb(pool string, name string) (uint64, error) {
|
||||
return uint64(info.SizeByte) / 1024 / 1024, nil
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) resizeImage(pool string, name string, sizeMb uint64) error {
|
||||
client, err := s.GetClient()
|
||||
func (s *SRbdStorage) resizeImage(name string, sizeMb uint64) error {
|
||||
client, err := s.getClient()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetClient")
|
||||
}
|
||||
defer client.Close()
|
||||
client.SetPool(pool)
|
||||
|
||||
img, err := client.GetImage(name)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetImage")
|
||||
@@ -209,13 +216,13 @@ func (s *SRbdStorage) resizeImage(pool string, name string, sizeMb uint64) error
|
||||
return img.Resize(int64(sizeMb))
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) deleteImage(pool string, name string, skipRecycle bool) error {
|
||||
client, err := s.GetClient()
|
||||
func (s *SRbdStorage) deleteImage(name string, skipRecycle bool) error {
|
||||
client, err := s.getClient()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetClient")
|
||||
}
|
||||
defer client.Close()
|
||||
client.SetPool(pool)
|
||||
|
||||
img, err := client.GetImage(name)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == errors.ErrNotFound {
|
||||
@@ -226,29 +233,35 @@ func (s *SRbdStorage) deleteImage(pool string, name string, skipRecycle bool) er
|
||||
return img.Delete()
|
||||
}
|
||||
|
||||
// 速度快
|
||||
func (s *SRbdStorage) cloneImage(ctx context.Context, srcPool string, srcImage string, destPool string, destImage string) error {
|
||||
client, err := s.GetClient()
|
||||
cli, err := s.getClient()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetClient")
|
||||
}
|
||||
defer client.Close()
|
||||
client.SetPool(srcPool)
|
||||
img, err := client.GetImage(srcImage)
|
||||
defer cli.Close()
|
||||
srcCli := cli.Child(srcPool)
|
||||
|
||||
img, err := srcCli.GetImage(srcImage)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetImage")
|
||||
}
|
||||
return img.Clone(ctx, destPool, destImage)
|
||||
_, err = img.Clone(ctx, destPool, destImage)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Clone")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) cloneFromSnapshot(srcImage, srcPool, srcSnapshot, newImage, pool string) error {
|
||||
client, err := s.GetClient()
|
||||
cli, err := s.getClient()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetClient")
|
||||
}
|
||||
defer client.Close()
|
||||
client.SetPool(srcPool)
|
||||
img, err := client.GetImage(srcImage)
|
||||
defer cli.Close()
|
||||
|
||||
srcCli := cli.Child(srcPool)
|
||||
|
||||
img, err := srcCli.GetImage(srcImage)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetImage(%s/%s)", srcPool, srcImage)
|
||||
}
|
||||
@@ -256,41 +269,46 @@ func (s *SRbdStorage) cloneFromSnapshot(srcImage, srcPool, srcSnapshot, newImage
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetSnapshot(%s)", srcSnapshot)
|
||||
}
|
||||
return snap.Clone(pool, newImage)
|
||||
_, err = snap.Clone(pool, newImage, true)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Clone")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) createImage(pool string, name string, sizeMb uint64) error {
|
||||
client, err := s.GetClient()
|
||||
func (s *SRbdStorage) createImage(name string, sizeMb uint64) error {
|
||||
client, err := s.getClient()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetClient")
|
||||
}
|
||||
defer client.Close()
|
||||
client.SetPool(pool)
|
||||
|
||||
_, err = client.CreateImage(name, int64(sizeMb))
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) renameImage(pool string, src string, dest string) error {
|
||||
client, err := s.GetClient()
|
||||
cli, err := s.getClient()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetClient")
|
||||
}
|
||||
defer client.Close()
|
||||
client.SetPool(pool)
|
||||
img, err := client.GetImage(src)
|
||||
defer cli.Close()
|
||||
|
||||
srcCli := cli.Child(pool)
|
||||
img, err := srcCli.GetImage(src)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetImage")
|
||||
}
|
||||
return img.Rename(dest)
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) resetDisk(pool string, diskId string, snapshotId string) error {
|
||||
client, err := s.GetClient()
|
||||
func (s *SRbdStorage) resetDisk(diskId string, snapshotId string) error {
|
||||
client, err := s.getClient()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetClient")
|
||||
}
|
||||
client.SetPool(pool)
|
||||
defer client.Close()
|
||||
|
||||
img, err := client.GetImage(diskId)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetImage")
|
||||
@@ -302,51 +320,44 @@ func (s *SRbdStorage) resetDisk(pool string, diskId string, snapshotId string) e
|
||||
return snap.Rollback()
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) createBackup(pool string, diskId string, snapshotId string, backupId string, backupStorageId string, backupStorageAccessInfo *jsonutils.JSONDict) (int, error) {
|
||||
client, err := s.GetClient()
|
||||
func (s *SRbdStorage) createBackup(ctx context.Context, diskId string, diskBackup *SDiskBackup) (int, error) {
|
||||
client, err := s.getClient()
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "GetClient")
|
||||
}
|
||||
client.SetPool(pool)
|
||||
defer client.Close()
|
||||
|
||||
img, err := client.GetImage(diskId)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "GetImage")
|
||||
}
|
||||
snap, err := img.GetSnapshot(snapshotId)
|
||||
snap, err := img.GetSnapshot(diskBackup.SnapshotId)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "unable to GetSnapshot %s of Image %s", snapshotId, diskId)
|
||||
return 0, errors.Wrapf(err, "unable to GetSnapshot %s of Image %s", diskBackup.SnapshotId, diskId)
|
||||
}
|
||||
backupName := fmt.Sprintf("backup_%s", backupId)
|
||||
err = snap.Clone(pool, backupName)
|
||||
backupName := fmt.Sprintf("backup_%s", diskBackup.BackupId)
|
||||
// no need to flattern, as image will be delete right after backup
|
||||
backupImg, err := snap.Clone(s.sStorageConf.Pool, backupName, false)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "unable to Clone snap %s", fmt.Sprintf("%s@%s", diskId, snapshotId))
|
||||
}
|
||||
backupImg, err := client.GetImage(backupName)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "GetImage")
|
||||
return 0, errors.Wrapf(err, "unable to Clone snap %s", fmt.Sprintf("%s@%s", diskId, diskBackup.SnapshotId))
|
||||
}
|
||||
defer backupImg.Delete()
|
||||
// convert backupStorage
|
||||
backupStorage, err := backupstorage.GetBackupStorage(backupStorageId, backupStorageAccessInfo)
|
||||
|
||||
srcPath := fmt.Sprintf("rbd:%s%s", backupImg.GetName(), s.getStorageConfString())
|
||||
|
||||
size, err := doBackupDisk(ctx, srcPath, diskBackup)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "unable to GetBackupStorage")
|
||||
return 0, errors.Wrap(err, "doBackupDisk")
|
||||
}
|
||||
srcPath := fmt.Sprintf("rbd:%s/%s%s", pool, backupName, s.getStorageConfString())
|
||||
// convert
|
||||
sizeMb, err := backupStorage.ConvertFrom(srcPath, qemuimgfmt.RAW, backupId)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, "unable to ConvertFrom with srcPath %s and format %s", srcPath, qemuimgfmt.RAW.String())
|
||||
}
|
||||
return sizeMb, nil
|
||||
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) createSnapshot(pool string, diskId string, snapshotId string) error {
|
||||
client, err := s.GetClient()
|
||||
func (s *SRbdStorage) createSnapshot(diskId string, snapshotId string) error {
|
||||
client, err := s.getClient()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetClient")
|
||||
}
|
||||
client.SetPool(pool)
|
||||
defer client.Close()
|
||||
img, err := client.GetImage(diskId)
|
||||
if err != nil {
|
||||
@@ -356,12 +367,11 @@ func (s *SRbdStorage) createSnapshot(pool string, diskId string, snapshotId stri
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) deleteSnapshot(pool string, diskId string, snapshotId string) error {
|
||||
client, err := s.GetClient()
|
||||
func (s *SRbdStorage) deleteSnapshot(diskId string, snapshotId string) error {
|
||||
client, err := s.getClient()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetClient")
|
||||
}
|
||||
client.SetPool(pool)
|
||||
defer client.Close()
|
||||
img, err := client.GetImage(diskId)
|
||||
if err != nil {
|
||||
@@ -379,7 +389,7 @@ func (s *SRbdStorage) SyncStorageSize() (api.SHostStorageStat, error) {
|
||||
StorageId: s.StorageId,
|
||||
}
|
||||
|
||||
client, err := s.GetClient()
|
||||
client, err := s.getClient()
|
||||
if err != nil {
|
||||
return stat, errors.Wrapf(err, "GetClient")
|
||||
}
|
||||
@@ -396,7 +406,7 @@ func (s *SRbdStorage) SyncStorageSize() (api.SHostStorageStat, error) {
|
||||
func (s *SRbdStorage) SyncStorageInfo() (jsonutils.JSONObject, error) {
|
||||
content := map[string]interface{}{}
|
||||
if len(s.StorageId) > 0 {
|
||||
client, err := s.GetClient()
|
||||
client, err := s.getClient()
|
||||
if err != nil {
|
||||
reason := jsonutils.Marshal(map[string]string{"reason": errors.Wrapf(err, "GetClient").Error()})
|
||||
return modules.Storages.PerformAction(hostutils.GetComputeSession(context.Background()), s.StorageId, api.STORAGE_OFFLINE, reason)
|
||||
@@ -449,7 +459,7 @@ func (s *SRbdStorage) CreateDisk(diskId string) IDisk {
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) Accessible() error {
|
||||
client, err := s.GetClient()
|
||||
client, err := s.getClient()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetClient")
|
||||
}
|
||||
@@ -508,15 +518,32 @@ func (s *SRbdStorage) saveToGlance(ctx context.Context, imageId, imagePath strin
|
||||
return err
|
||||
}
|
||||
|
||||
tmpImageFile := fmt.Sprintf("/tmp/%s.img", imageId)
|
||||
tmpFileDir, err := ioutil.TempDir(options.HostOptions.TempPath, "ceph_save_images")
|
||||
if err != nil {
|
||||
log.Errorf("fail to obtain tempFile for ceph save glance image: %s", err)
|
||||
return errors.Wrap(err, "ioutil.TempDir")
|
||||
}
|
||||
defer func() {
|
||||
log.Debugf("clean up temp dir for glance image save %s", tmpFileDir)
|
||||
output, err := procutils.NewRemoteCommandAsFarAsPossible("rm", "-fr", tmpFileDir).Output()
|
||||
if err != nil {
|
||||
log.Errorf("rm %s fail %s %s", tmpFileDir, output, err)
|
||||
}
|
||||
}()
|
||||
|
||||
tmpImageFile := filepath.Join(tmpFileDir, imageId)
|
||||
|
||||
if len(format) == 0 {
|
||||
format = options.HostOptions.DefaultImageSaveFormat
|
||||
}
|
||||
|
||||
err = procutils.NewRemoteCommandAsFarAsPossible(qemutils.GetQemuImg(),
|
||||
"convert", "-f", "raw", "-O", format, imagePath, tmpImageFile).Run()
|
||||
img, err := qemuimg.NewQemuImage(imagePath)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrapf(err, "NewQemuImage %s", imagePath)
|
||||
}
|
||||
_, err = img.Clone(tmpImageFile, qemuimgfmt.String2ImageFormat(format), compress)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Clone %s", tmpImageFile)
|
||||
}
|
||||
|
||||
f, err := os.Open(tmpImageFile)
|
||||
@@ -551,8 +578,7 @@ func (s *SRbdStorage) saveToGlance(ctx context.Context, imageId, imagePath strin
|
||||
}
|
||||
params.Set("image_id", jsonutils.NewString(imageId))
|
||||
|
||||
_, err = image.Images.Upload(hostutils.GetImageSession(ctx),
|
||||
params, f, size)
|
||||
_, err = image.Images.Upload(hostutils.GetImageSession(ctx), params, f, size)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -578,9 +604,13 @@ func (s *SRbdStorage) CreateDiskFromExistingPath(context.Context, IDisk, *SDiskC
|
||||
}
|
||||
|
||||
func (s *SRbdStorage) CreateDiskFromBackup(ctx context.Context, disk IDisk, input *SDiskCreateByDiskinfo) error {
|
||||
backup := input.DiskInfo.Backup
|
||||
pool, _ := s.StorageConf.GetString("pool")
|
||||
destPath := fmt.Sprintf("rbd:%s/%s%s", pool, disk.GetId(), s.getStorageConfString())
|
||||
err := doRestoreDisk(ctx, input.DiskInfo, destPath, input.DiskInfo.Format)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "doRestore")
|
||||
}
|
||||
/*backup := input.DiskInfo.Backup
|
||||
backupStorage, err := backupstorage.GetBackupStorage(backup.BackupStorageId, backup.BackupStorageAccessInfo)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to GetBackupStorage")
|
||||
@@ -588,7 +618,7 @@ func (s *SRbdStorage) CreateDiskFromBackup(ctx context.Context, disk IDisk, inpu
|
||||
err = backupStorage.ConvertTo(destPath, qemuimgfmt.RAW, backup.BackupId)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to Convert to with destPath %s and format %s", destPath, qemuimgfmt.RAW.String())
|
||||
}
|
||||
}*/
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -621,7 +651,7 @@ func (s *SRbdStorage) CloneDiskFromStorage(
|
||||
return nil, errors.Wrap(err, "Clone source disk to target rbd storage")
|
||||
}
|
||||
} else {
|
||||
err = s.createImage(s.Pool, targetDiskId, uint64(srcImg.GetSizeMB()))
|
||||
err = s.createImage(targetDiskId, uint64(srcImg.GetSizeMB()))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Create rbd image")
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ func storageSyncBackup(ctx context.Context, w http.ResponseWriter, r *http.Reque
|
||||
hostutils.Response(ctx, w, err)
|
||||
return
|
||||
}
|
||||
exist, err := backupStorage.IsExists(backupId)
|
||||
exist, err := backupStorage.IsBackupExists(backupId)
|
||||
if err != nil {
|
||||
hostutils.Response(ctx, w, err)
|
||||
return
|
||||
@@ -344,13 +344,9 @@ func storageUnpackInstanceBackup(ctx context.Context, w http.ResponseWriter, r *
|
||||
|
||||
func packInstanceBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
|
||||
sbParams := params.(*storageman.SStoragePackInstanceBackup)
|
||||
backupStorage, err := backupstorage.GetBackupStorage(sbParams.BackupStorageId, sbParams.BackupStorageAccessInfo)
|
||||
packFileName, err := storageman.DoInstancePackBackup(ctx, *sbParams)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetBackupStorage")
|
||||
}
|
||||
packFileName, err := backupStorage.InstancePack(ctx, sbParams.PackageName, sbParams.BackupIds, &sbParams.Metadata)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "InstancePack")
|
||||
return nil, errors.Wrap(err, "DoInstancePackBackup")
|
||||
}
|
||||
ret := jsonutils.NewDict()
|
||||
ret.Set("pack_file_name", jsonutils.NewString(packFileName))
|
||||
@@ -359,15 +355,12 @@ func packInstanceBackup(ctx context.Context, params interface{}) (jsonutils.JSON
|
||||
|
||||
func unpackInstanceBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject, error) {
|
||||
sbParams := params.(*storageman.SStorageUnpackInstanceBackup)
|
||||
backupStorage, err := backupstorage.GetBackupStorage(sbParams.BackupStorageId, sbParams.BackupStorageAccessInfo)
|
||||
|
||||
diskBackupIds, metadata, err := storageman.DoInstanceUnpackBackup(ctx, *sbParams)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetBackupStorage")
|
||||
}
|
||||
metadataOnly := (sbParams.MetadataOnly != nil && *sbParams.MetadataOnly)
|
||||
diskBackupIds, metadata, err := backupStorage.InstanceUnpack(ctx, sbParams.PackageName, metadataOnly)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "InstanceUnpack")
|
||||
return nil, errors.Wrap(err, "DoInstanceUnpackBackup")
|
||||
}
|
||||
|
||||
ret := jsonutils.NewDict()
|
||||
if diskBackupIds != nil {
|
||||
ret.Set("disk_backup_ids", jsonutils.Marshal(diskBackupIds))
|
||||
@@ -418,7 +411,7 @@ func deleteBackup(ctx context.Context, params interface{}) (jsonutils.JSONObject
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = backupStorage.RemoveBackup(sbParams.BackupId)
|
||||
err = backupStorage.RemoveBackup(ctx, sbParams.BackupId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ type SDiskCleanupSnapshots struct {
|
||||
DeleteSnapshots []jsonutils.JSONObject
|
||||
}
|
||||
|
||||
type SDiskBakcup struct {
|
||||
type SDiskBackup struct {
|
||||
SnapshotId string `json:"snapshot_id"`
|
||||
BackupId string `json:"backup_id"`
|
||||
BackupStorageId string `json:"backup_storage_id"`
|
||||
|
||||
@@ -678,6 +678,9 @@ func (manager *SGuestImageManager) ListItemFilter(
|
||||
q = q.IsFalse("protected")
|
||||
}
|
||||
}
|
||||
if len(query.DiskFormat) > 0 {
|
||||
q = q.In("disk_format", query.DiskFormat)
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -112,16 +112,37 @@ func (opts *BackupStorageIdOptions) Params() (jsonutils.JSONObject, error) {
|
||||
|
||||
type BackupStorageCreateOptions struct {
|
||||
options.BaseCreateOptions
|
||||
StorageType string `help:"storage type" choices:"nfs"`
|
||||
StorageType string `help:"storage type" choices:"nfs|object"`
|
||||
|
||||
NfsHost string `help:"nfs host, required when storage_type is nfs"`
|
||||
NfsSharedDir string `help:"nfs shared dir, required when storage_type is nfs" `
|
||||
CapacityMb int `help:"capacity, unit mb"`
|
||||
|
||||
ObjectBucketUrl string `help:"object bucket url, required when storage_type is object"`
|
||||
ObjectAccessKey string `help:"object storage access key, required when storage_type is object"`
|
||||
ObjectSecret string `help:"object storage secret, required when storage_type is object"`
|
||||
|
||||
CapacityMb int `help:"capacity, unit mb"`
|
||||
}
|
||||
|
||||
func (opts *BackupStorageCreateOptions) Params() (jsonutils.JSONObject, error) {
|
||||
return jsonutils.Marshal(opts), nil
|
||||
}
|
||||
|
||||
type BackupStorageUpdateOptions struct {
|
||||
options.BaseUpdateOptions
|
||||
|
||||
NfsHost string `help:"nfs host, required when storage_type is nfs"`
|
||||
NfsSharedDir string `help:"nfs shared dir, required when storage_type is nfs" `
|
||||
|
||||
ObjectBucketUrl string `help:"object bucket url, required when storage_type is object"`
|
||||
ObjectAccessKey string `help:"object storage access key, required when storage_type is object"`
|
||||
ObjectSecret string `help:"object storage secret, required when storage_type is object"`
|
||||
}
|
||||
|
||||
func (opts *BackupStorageUpdateOptions) Params() (jsonutils.JSONObject, error) {
|
||||
return jsonutils.Marshal(opts), nil
|
||||
}
|
||||
|
||||
type InstanceBackupListOptions struct {
|
||||
options.BaseListOptions
|
||||
|
||||
|
||||
+161
-162
@@ -42,15 +42,11 @@ type CephClient struct {
|
||||
timeout int
|
||||
}
|
||||
|
||||
func (self *CephClient) Close() error {
|
||||
if len(self.keyConf) > 0 {
|
||||
os.Remove(self.keyConf)
|
||||
func (cli *CephClient) Close() error {
|
||||
if len(cli.keyConf) > 0 {
|
||||
os.Remove(cli.keyConf)
|
||||
}
|
||||
return os.Remove(self.cephConf)
|
||||
}
|
||||
|
||||
func (self *CephClient) SetPool(pool string) {
|
||||
self.pool = pool
|
||||
return os.Remove(cli.cephConf)
|
||||
}
|
||||
|
||||
type cephStats struct {
|
||||
@@ -91,15 +87,15 @@ type SCapacity struct {
|
||||
UsedCapacitySizeKb int64
|
||||
}
|
||||
|
||||
func (self *CephClient) Output(name string, opts []string) (jsonutils.JSONObject, error) {
|
||||
return self.output(name, opts, false)
|
||||
func (cli *CephClient) Output(name string, opts []string) (jsonutils.JSONObject, error) {
|
||||
return cli.output(name, opts, false)
|
||||
}
|
||||
|
||||
func (self *CephClient) output(name string, opts []string, timeout bool) (jsonutils.JSONObject, error) {
|
||||
func (cli *CephClient) output(name string, opts []string, timeout bool) (jsonutils.JSONObject, error) {
|
||||
cmds := []string{name, "--format", "json"}
|
||||
cmds = append(cmds, opts...)
|
||||
if timeout {
|
||||
cmds = append([]string{"timeout", "--signal=KILL", fmt.Sprintf("%ds", self.timeout)}, cmds...)
|
||||
cmds = append([]string{"timeout", "--signal=KILL", fmt.Sprintf("%ds", cli.timeout)}, cmds...)
|
||||
}
|
||||
proc := procutils.NewRemoteCommandAsFarAsPossible(cmds[0], cmds[1:]...)
|
||||
outb, err := proc.StdoutPipe()
|
||||
@@ -133,10 +129,10 @@ func (self *CephClient) output(name string, opts []string, timeout bool) (jsonut
|
||||
return jsonutils.Parse(stdoutPut)
|
||||
}
|
||||
|
||||
func (self *CephClient) run(name string, opts []string, timeout bool) error {
|
||||
func (cli *CephClient) run(name string, opts []string, timeout bool) error {
|
||||
cmds := append([]string{name}, opts...)
|
||||
if timeout {
|
||||
cmds = append([]string{"timeout", "--signal=KILL", fmt.Sprintf("%ds", self.timeout)}, cmds...)
|
||||
cmds = append([]string{"timeout", "--signal=KILL", fmt.Sprintf("%ds", cli.timeout)}, cmds...)
|
||||
}
|
||||
output, err := procutils.NewRemoteCommandAsFarAsPossible(cmds[0], cmds[1:]...).Output()
|
||||
if err != nil {
|
||||
@@ -145,26 +141,26 @@ func (self *CephClient) run(name string, opts []string, timeout bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CephClient) options() []string {
|
||||
opts := []string{"--conf", self.cephConf}
|
||||
if len(self.keyConf) > 0 {
|
||||
opts = append(opts, []string{"--keyring", self.keyConf}...)
|
||||
func (cli *CephClient) options() []string {
|
||||
opts := []string{"--conf", cli.cephConf}
|
||||
if len(cli.keyConf) > 0 {
|
||||
opts = append(opts, []string{"--keyring", cli.keyConf}...)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func (self *CephClient) CreateImage(name string, sizeMb int64) (*SImage, error) {
|
||||
opts := self.options()
|
||||
image := &SImage{name: name, client: self}
|
||||
func (cli *CephClient) CreateImage(name string, sizeMb int64) (*SImage, error) {
|
||||
opts := cli.options()
|
||||
image := &SImage{name: name, client: cli}
|
||||
opts = append(opts, []string{"create", image.GetName(), "--size", fmt.Sprintf("%dM", sizeMb)}...)
|
||||
return image, self.run("rbd", opts, false)
|
||||
return image, cli.run("rbd", opts, false)
|
||||
}
|
||||
|
||||
func (self *CephClient) GetCapacity() (*SCapacity, error) {
|
||||
func (cli *CephClient) GetCapacity() (*SCapacity, error) {
|
||||
result := &SCapacity{}
|
||||
opts := self.options()
|
||||
opts := cli.options()
|
||||
opts = append(opts, "df")
|
||||
resp, err := self.output("ceph", opts, true)
|
||||
resp, err := cli.output("ceph", opts, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "output")
|
||||
}
|
||||
@@ -176,7 +172,7 @@ func (self *CephClient) GetCapacity() (*SCapacity, error) {
|
||||
result.CapacitySizeKb = stats.Stats.TotalBytes / 1024
|
||||
result.UsedCapacitySizeKb = stats.Stats.TotalUsedBytes / 1024
|
||||
for _, pool := range stats.Pools {
|
||||
if pool.Name == self.pool {
|
||||
if pool.Name == cli.pool {
|
||||
result.UsedCapacitySizeKb = int64(pool.Stats.Stored / 1024)
|
||||
if pool.Stats.MaxAvail > 0 {
|
||||
result.CapacitySizeKb = int64(pool.Stats.MaxAvail/1024 + int64(pool.Stats.Stored/1024))
|
||||
@@ -270,20 +266,26 @@ keyring = %s
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (cli *CephClient) Child(pool string) *CephClient {
|
||||
newCli := *cli
|
||||
newCli.pool = pool
|
||||
return &newCli
|
||||
}
|
||||
|
||||
type SImage struct {
|
||||
name string
|
||||
client *CephClient
|
||||
}
|
||||
|
||||
func (self *SImage) GetName() string {
|
||||
return fmt.Sprintf("%s/%s", self.client.pool, self.name)
|
||||
func (img *SImage) GetName() string {
|
||||
return fmt.Sprintf("%s/%s", img.client.pool, img.name)
|
||||
}
|
||||
|
||||
func (self *CephClient) ListImages() ([]string, error) {
|
||||
func (cli *CephClient) ListImages() ([]string, error) {
|
||||
result := []string{}
|
||||
opts := self.options()
|
||||
opts = append(opts, []string{"ls", self.pool}...)
|
||||
resp, err := self.output("rbd", opts, true)
|
||||
opts := cli.options()
|
||||
opts = append(opts, []string{"ls", cli.pool}...)
|
||||
resp, err := cli.output("rbd", opts, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -294,15 +296,15 @@ func (self *CephClient) ListImages() ([]string, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (self *CephClient) GetImage(name string) (*SImage, error) {
|
||||
images, err := self.ListImages()
|
||||
func (cli *CephClient) GetImage(name string) (*SImage, error) {
|
||||
images, err := cli.ListImages()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "ListImages")
|
||||
}
|
||||
if !utils.IsInStringArray(name, images) {
|
||||
return nil, cloudprovider.ErrNotFound
|
||||
}
|
||||
return &SImage{name: name, client: self}, nil
|
||||
return &SImage{name: name, client: cli}, nil
|
||||
}
|
||||
|
||||
type SImageInfo struct {
|
||||
@@ -323,14 +325,14 @@ type SImageInfo struct {
|
||||
ModifyTimestamp string `json:"modify_timestamp"`
|
||||
}
|
||||
|
||||
func (self *SImage) options() []string {
|
||||
return self.client.options()
|
||||
func (img *SImage) options() []string {
|
||||
return img.client.options()
|
||||
}
|
||||
|
||||
func (self *SImage) GetInfo() (*SImageInfo, error) {
|
||||
opts := self.options()
|
||||
opts = append(opts, []string{"info", self.GetName()}...)
|
||||
resp, err := self.client.output("rbd", opts, true)
|
||||
func (img *SImage) GetInfo() (*SImageInfo, error) {
|
||||
opts := img.options()
|
||||
opts = append(opts, []string{"info", img.GetName()}...)
|
||||
resp, err := img.client.output("rbd", opts, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -338,10 +340,10 @@ func (self *SImage) GetInfo() (*SImageInfo, error) {
|
||||
return info, resp.Unmarshal(info)
|
||||
}
|
||||
|
||||
func (self *SImage) ListSnapshots() ([]SSnapshot, error) {
|
||||
opts := self.options()
|
||||
opts = append(opts, []string{"snap", "ls", self.GetName()}...)
|
||||
resp, err := self.client.output("rbd", opts, true)
|
||||
func (img *SImage) ListSnapshots() ([]SSnapshot, error) {
|
||||
opts := img.options()
|
||||
opts = append(opts, []string{"snap", "ls", img.GetName()}...)
|
||||
resp, err := img.client.output("rbd", opts, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -351,13 +353,13 @@ func (self *SImage) ListSnapshots() ([]SSnapshot, error) {
|
||||
return nil, errors.Wrapf(err, "ret.Unmarshal")
|
||||
}
|
||||
for i := range result {
|
||||
result[i].image = self
|
||||
result[i].image = img
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (self *SImage) GetSnapshot(name string) (*SSnapshot, error) {
|
||||
snaps, err := self.ListSnapshots()
|
||||
func (img *SImage) GetSnapshot(name string) (*SSnapshot, error) {
|
||||
snaps, err := img.ListSnapshots()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "ListSnapshots")
|
||||
}
|
||||
@@ -369,8 +371,8 @@ func (self *SImage) GetSnapshot(name string) (*SSnapshot, error) {
|
||||
return nil, cloudprovider.ErrNotFound
|
||||
}
|
||||
|
||||
func (self *SImage) IsSnapshotExist(name string) (bool, error) {
|
||||
_, err := self.GetSnapshot(name)
|
||||
func (img *SImage) IsSnapshotExist(name string) (bool, error) {
|
||||
_, err := img.GetSnapshot(name)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == cloudprovider.ErrNotFound {
|
||||
return false, nil
|
||||
@@ -381,74 +383,78 @@ func (self *SImage) IsSnapshotExist(name string) (bool, error) {
|
||||
}
|
||||
|
||||
type SSnapshot struct {
|
||||
Name string
|
||||
Id string
|
||||
Size int64
|
||||
Protected bool
|
||||
Name string
|
||||
Id string
|
||||
Size int64
|
||||
// Protected bool
|
||||
Timestamp string
|
||||
|
||||
image *SImage
|
||||
}
|
||||
|
||||
func (self *SSnapshot) Rollback() error {
|
||||
opts := self.options()
|
||||
opts = append(opts, []string{"snap", "rollback", self.GetName()}...)
|
||||
return self.image.client.run("rbd", opts, false)
|
||||
func (snap *SSnapshot) Rollback() error {
|
||||
opts := snap.options()
|
||||
opts = append(opts, []string{"snap", "rollback", snap.GetName()}...)
|
||||
return snap.image.client.run("rbd", opts, false)
|
||||
}
|
||||
|
||||
func (self *SSnapshot) options() []string {
|
||||
return self.image.options()
|
||||
func (snap *SSnapshot) options() []string {
|
||||
return snap.image.options()
|
||||
}
|
||||
|
||||
func (self *SSnapshot) GetName() string {
|
||||
return fmt.Sprintf("%s@%s", self.image.GetName(), self.Name)
|
||||
func (snap *SSnapshot) GetName() string {
|
||||
return fmt.Sprintf("%s@%s", snap.image.GetName(), snap.Name)
|
||||
}
|
||||
|
||||
func (self *SSnapshot) Unprotect() error {
|
||||
opts := self.options()
|
||||
opts = append(opts, []string{"snap", "unprotect", self.GetName()}...)
|
||||
err := self.image.client.run("rbd", opts, true)
|
||||
func (snap *SSnapshot) Unprotect() error {
|
||||
opts := snap.options()
|
||||
opts = append(opts, []string{"snap", "unprotect", snap.GetName()}...)
|
||||
err := snap.image.client.run("rbd", opts, true)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "snap is already unprotected") {
|
||||
// snap.Protected = false
|
||||
return nil
|
||||
}
|
||||
return errors.Wrapf(err, "Unprotect")
|
||||
return errors.Wrapf(err, "unprotect")
|
||||
}
|
||||
self.Protected = false
|
||||
// snap.Protected = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SSnapshot) Protect() error {
|
||||
if self.Protected {
|
||||
return nil
|
||||
}
|
||||
opts := self.options()
|
||||
opts = append(opts, []string{"snap", "protect", self.GetName()}...)
|
||||
err := self.image.client.run("rbd", opts, true)
|
||||
if err == nil {
|
||||
self.Protected = true
|
||||
func (snap *SSnapshot) protect() error {
|
||||
// if snap.Protected {
|
||||
// return nil
|
||||
// }
|
||||
opts := snap.options()
|
||||
opts = append(opts, []string{"snap", "protect", snap.GetName()}...)
|
||||
err := snap.image.client.run("rbd", opts, true)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "snap is already protected") {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err, "protect")
|
||||
}
|
||||
// if err == nil {
|
||||
// snap.Protected = true
|
||||
// }
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *SSnapshot) Remove() error {
|
||||
opts := self.options()
|
||||
opts = append(opts, []string{"snap", "rm", self.GetName()}...)
|
||||
return self.image.client.run("rbd", opts, false)
|
||||
func (snap *SSnapshot) Remove() error {
|
||||
opts := snap.options()
|
||||
opts = append(opts, []string{"snap", "rm", snap.GetName()}...)
|
||||
return snap.image.client.run("rbd", opts, false)
|
||||
}
|
||||
|
||||
func (self *SSnapshot) Delete() error {
|
||||
pool := self.image.client.pool
|
||||
defer self.image.client.SetPool(pool)
|
||||
|
||||
children, err := self.ListChildren()
|
||||
func (snap *SSnapshot) Delete() error {
|
||||
children, err := snap.ListChildren()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "ListChildren")
|
||||
}
|
||||
|
||||
for i := range children {
|
||||
self.image.client.SetPool(children[i].Pool)
|
||||
image, err := self.image.client.GetImage(children[i].Image)
|
||||
tmpCli := snap.image.client.Child(children[i].Pool)
|
||||
image, err := tmpCli.GetImage(children[i].Image)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetImage(%s/%s)", children[i].Pool, children[i].Image)
|
||||
}
|
||||
@@ -458,12 +464,13 @@ func (self *SSnapshot) Delete() error {
|
||||
}
|
||||
}
|
||||
|
||||
err = self.Unprotect()
|
||||
// always try to unprotect
|
||||
err = snap.Unprotect()
|
||||
if err != nil {
|
||||
log.Errorf("Unprotect %s failed: %s", self.GetName(), err)
|
||||
log.Errorf("Unprotect %s failed: %s", snap.GetName(), err)
|
||||
}
|
||||
|
||||
return self.Remove()
|
||||
return snap.Remove()
|
||||
}
|
||||
|
||||
type SChildren struct {
|
||||
@@ -472,10 +479,10 @@ type SChildren struct {
|
||||
Image string
|
||||
}
|
||||
|
||||
func (self *SSnapshot) ListChildren() ([]SChildren, error) {
|
||||
opts := self.options()
|
||||
opts = append(opts, []string{"children", self.GetName()}...)
|
||||
resp, err := self.image.client.output("rbd", opts, true)
|
||||
func (snap *SSnapshot) ListChildren() ([]SChildren, error) {
|
||||
opts := snap.options()
|
||||
opts = append(opts, []string{"children", snap.GetName()}...)
|
||||
resp, err := snap.image.client.output("rbd", opts, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "ListChildren")
|
||||
}
|
||||
@@ -483,26 +490,26 @@ func (self *SSnapshot) ListChildren() ([]SChildren, error) {
|
||||
return chidren, resp.Unmarshal(&chidren)
|
||||
}
|
||||
|
||||
func (self *SImage) Resize(sizeMb int64) error {
|
||||
opts := self.options()
|
||||
opts = append(opts, []string{"resize", self.GetName(), "--size", fmt.Sprintf("%dM", sizeMb)}...)
|
||||
return self.client.run("rbd", opts, false)
|
||||
func (img *SImage) Resize(sizeMb int64) error {
|
||||
opts := img.options()
|
||||
opts = append(opts, []string{"resize", img.GetName(), "--size", fmt.Sprintf("%dM", sizeMb)}...)
|
||||
return img.client.run("rbd", opts, false)
|
||||
}
|
||||
|
||||
func (self *SImage) Remove() error {
|
||||
opts := self.options()
|
||||
opts = append(opts, []string{"rm", self.GetName()}...)
|
||||
return self.client.run("rbd", opts, false)
|
||||
func (img *SImage) Remove() error {
|
||||
opts := img.options()
|
||||
opts = append(opts, []string{"rm", img.GetName()}...)
|
||||
return img.client.run("rbd", opts, false)
|
||||
}
|
||||
|
||||
func (self *SImage) Flatten() error {
|
||||
opts := self.options()
|
||||
opts = append(opts, []string{"flatten", self.GetName()}...)
|
||||
return self.client.run("rbd", opts, false)
|
||||
func (img *SImage) Flatten() error {
|
||||
opts := img.options()
|
||||
opts = append(opts, []string{"flatten", img.GetName()}...)
|
||||
return img.client.run("rbd", opts, false)
|
||||
}
|
||||
|
||||
func (self *SImage) Delete() error {
|
||||
snapshots, err := self.ListSnapshots()
|
||||
func (img *SImage) Delete() error {
|
||||
snapshots, err := img.ListSnapshots()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "ListSnapshots")
|
||||
}
|
||||
@@ -512,77 +519,69 @@ func (self *SImage) Delete() error {
|
||||
return errors.Wrapf(err, "delete snapshot %s", snapshots[i].GetName())
|
||||
}
|
||||
}
|
||||
return self.Remove()
|
||||
return img.Remove()
|
||||
}
|
||||
|
||||
func (self *SImage) Rename(name string) error {
|
||||
opts := self.options()
|
||||
opts = append(opts, []string{"rename", self.GetName(), fmt.Sprintf("%s/%s", self.client.pool, name)}...)
|
||||
return self.client.run("rbd", opts, false)
|
||||
func (img *SImage) Rename(name string) error {
|
||||
opts := img.options()
|
||||
opts = append(opts, []string{"rename", img.GetName(), fmt.Sprintf("%s/%s", img.client.pool, name)}...)
|
||||
return img.client.run("rbd", opts, false)
|
||||
}
|
||||
|
||||
func (self *SImage) CreateSnapshot(name string) (*SSnapshot, error) {
|
||||
snap := &SSnapshot{Name: name, image: self}
|
||||
opts := self.options()
|
||||
func (img *SImage) CreateSnapshot(name string) (*SSnapshot, error) {
|
||||
snap := &SSnapshot{Name: name, image: img}
|
||||
opts := img.options()
|
||||
opts = append(opts, []string{"snap", "create", snap.GetName()}...)
|
||||
if err := self.client.run("rbd", opts, false); err != nil {
|
||||
if err := img.client.run("rbd", opts, false); err != nil {
|
||||
return nil, errors.Wrap(err, "snap create")
|
||||
}
|
||||
if err := snap.Protect(); err != nil {
|
||||
log.Errorf("failed protect snap %s: %s", snap.GetName(), err)
|
||||
}
|
||||
return snap, nil
|
||||
}
|
||||
|
||||
func (self *SImage) Clone(ctx context.Context, pool, name string) error {
|
||||
lockman.LockRawObject(ctx, "rbd_image_cache", self.GetName())
|
||||
defer lockman.ReleaseRawObject(ctx, "rbd_image_cache", self.GetName())
|
||||
func (img *SImage) Clone(ctx context.Context, pool, name string) (*SImage, error) {
|
||||
lockman.LockRawObject(ctx, "rbd_image_cache", img.GetName())
|
||||
defer lockman.ReleaseRawObject(ctx, "rbd_image_cache", img.GetName())
|
||||
|
||||
var findOrCreateSnap = func() (*SSnapshot, error) {
|
||||
snaps, err := self.ListSnapshots()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "ListSnapshots")
|
||||
}
|
||||
for i := range snaps {
|
||||
if snaps[i].Name == name {
|
||||
return &snaps[i], nil
|
||||
}
|
||||
}
|
||||
snap, err := self.CreateSnapshot(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "CreateSnapshot")
|
||||
}
|
||||
return snap, nil
|
||||
}
|
||||
snap, err := findOrCreateSnap()
|
||||
tmpSnapName := "snap-" + utils.GenRequestId(12)
|
||||
tmpSnap, err := img.CreateSnapshot(tmpSnapName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "findOrCreateSnap")
|
||||
return nil, errors.Wrapf(err, "CreateSnapshot")
|
||||
}
|
||||
err = snap.Clone(pool, name)
|
||||
defer tmpSnap.Delete()
|
||||
|
||||
newimg, err := tmpSnap.Clone(pool, name, true)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "clone %s/%s", pool, name)
|
||||
return nil, errors.Wrapf(err, "clone %s/%s", pool, name)
|
||||
}
|
||||
|
||||
_pool := self.client.pool
|
||||
|
||||
// use current pool
|
||||
self.client.SetPool(pool)
|
||||
// recover previous pool
|
||||
defer self.client.SetPool(_pool)
|
||||
|
||||
img, err := self.client.GetImage(name)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetImage(%s) after clone", name)
|
||||
}
|
||||
return img.Flatten()
|
||||
return newimg, nil
|
||||
}
|
||||
|
||||
func (self *SSnapshot) Clone(pool, name string) error {
|
||||
err := self.Protect()
|
||||
func (snap *SSnapshot) Clone(pool, name string, flattern bool) (*SImage, error) {
|
||||
err := snap.protect()
|
||||
if err != nil {
|
||||
log.Warningf("protect %s error: %v", self.GetName(), err)
|
||||
log.Warningf("protect %s error: %v", snap.GetName(), err)
|
||||
return nil, errors.Wrap(err, "Protect")
|
||||
}
|
||||
opts := self.options()
|
||||
opts = append(opts, []string{"clone", self.GetName(), fmt.Sprintf("%s/%s", pool, name)}...)
|
||||
return self.image.client.run("rbd", opts, false)
|
||||
if flattern {
|
||||
defer snap.Unprotect()
|
||||
}
|
||||
|
||||
opts := snap.options()
|
||||
opts = append(opts, []string{"clone", snap.GetName(), fmt.Sprintf("%s/%s", pool, name)}...)
|
||||
err = snap.image.client.run("rbd", opts, false)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "clone")
|
||||
}
|
||||
newimg, err := snap.image.client.Child(pool).GetImage(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "GetImage(%s) after clone", name)
|
||||
}
|
||||
if flattern {
|
||||
err = newimg.Flatten()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "flattern")
|
||||
}
|
||||
}
|
||||
return newimg, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user