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:
Zexi Li
2023-11-26 21:05:16 +08:00
committed by GitHub
43 changed files with 1719 additions and 621 deletions
+1
View File
@@ -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
View File
@@ -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
}
+8
View File
@@ -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{}
})
}
-43
View File
@@ -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"`
}
-46
View File
@@ -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"`
}
-2
View File
@@ -73,8 +73,6 @@ type ServerListInput struct {
// 列出操作系统为指定值的主机
// enum: linux,windows,vmware
OsType []string `json:"os_type"`
// 操作系统发行版
OsDist []string `json:"os_dist"`
// 对列表结果按照磁盘大小进行排序
// enum: asc,desc
+1 -1
View File
@@ -17,7 +17,7 @@ package compute
type HostwireDetails struct {
HostJointResourceDetails
SHostwire
SHostwireDeprecated
// 二层网络名称
Wire string `json:"wire"`
+87 -62
View File
@@ -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
+24 -5
View File
@@ -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
+2
View File
@@ -66,6 +66,8 @@ type GuestImageListInput struct {
// 是否删除保护
Protected *bool `json:"protected"`
DiskFormat []string `json:"disk_format"`
}
type ImageDetails struct {
+10 -4
View File
@@ -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"`
}
+2 -3
View File
@@ -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
}
}
+153 -33
View File
@@ -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
}
+5 -1
View File
@@ -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
}
-11
View File
@@ -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)
+4
View File
@@ -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
}
}
}
+2 -2
View File
@@ -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())
+36 -12
View File
@@ -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)
}
+294
View File
@@ -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
}
+5 -7
View File
@@ -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")
}
+13 -48
View File
@@ -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)
}
+6
View File
@@ -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),
+2
View File
@@ -31,6 +31,8 @@ import (
"yunion.io/x/onecloud/pkg/util/qemuimg"
)
var _ IDisk = (*SNasDisk)(nil)
type SNasDisk struct {
SLocalDisk
}
+7
View File
@@ -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),
+20 -25
View File
@@ -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")
+37 -7
View File
@@ -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
+13 -8
View File
@@ -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
+109 -79
View File
@@ -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
}
+1 -1
View File
@@ -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"`
+3
View File
@@ -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
}
+23 -2
View File
@@ -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
View File
@@ -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
}