Skip to content

Commit 9b22cd5

Browse files
Download Volume Snapshots (#8878)
Co-authored-by: Rodrigo D. Lopez <[email protected]>
1 parent f5c7729 commit 9b22cd5

File tree

19 files changed

+417
-60
lines changed

19 files changed

+417
-60
lines changed

api/src/main/java/com/cloud/event/EventTypes.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ public class EventTypes {
333333
public static final String EVENT_SNAPSHOT_OFF_PRIMARY = "SNAPSHOT.OFF_PRIMARY";
334334
public static final String EVENT_SNAPSHOT_DELETE = "SNAPSHOT.DELETE";
335335
public static final String EVENT_SNAPSHOT_REVERT = "SNAPSHOT.REVERT";
336+
public static final String EVENT_SNAPSHOT_EXTRACT = "SNAPSHOT.EXTRACT";
336337
public static final String EVENT_SNAPSHOT_POLICY_CREATE = "SNAPSHOTPOLICY.CREATE";
337338
public static final String EVENT_SNAPSHOT_POLICY_UPDATE = "SNAPSHOTPOLICY.UPDATE";
338339
public static final String EVENT_SNAPSHOT_POLICY_DELETE = "SNAPSHOTPOLICY.DELETE";
@@ -897,6 +898,7 @@ public class EventTypes {
897898
// Snapshots
898899
entityEventDetails.put(EVENT_SNAPSHOT_CREATE, Snapshot.class);
899900
entityEventDetails.put(EVENT_SNAPSHOT_DELETE, Snapshot.class);
901+
entityEventDetails.put(EVENT_SNAPSHOT_EXTRACT, Snapshot.class);
900902
entityEventDetails.put(EVENT_SNAPSHOT_ON_PRIMARY, Snapshot.class);
901903
entityEventDetails.put(EVENT_SNAPSHOT_OFF_PRIMARY, Snapshot.class);
902904
entityEventDetails.put(EVENT_SNAPSHOT_POLICY_CREATE, SnapshotPolicy.class);

api/src/main/java/com/cloud/storage/Upload.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public static enum Status {
4040
}
4141

4242
public static enum Type {
43-
VOLUME, TEMPLATE, ISO
43+
VOLUME, SNAPSHOT, TEMPLATE, ISO
4444
}
4545

4646
public static enum Mode {

api/src/main/java/com/cloud/storage/snapshot/SnapshotApiService.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.apache.cloudstack.api.command.user.snapshot.CopySnapshotCmd;
2222
import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotPolicyCmd;
2323
import org.apache.cloudstack.api.command.user.snapshot.DeleteSnapshotPoliciesCmd;
24+
import org.apache.cloudstack.api.command.user.snapshot.ExtractSnapshotCmd;
2425
import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotPoliciesCmd;
2526
import org.apache.cloudstack.api.command.user.snapshot.ListSnapshotsCmd;
2627
import org.apache.cloudstack.api.command.user.snapshot.UpdateSnapshotPolicyCmd;
@@ -106,6 +107,16 @@ Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapsh
106107
*/
107108
Snapshot createSnapshot(Long volumeId, Long policyId, Long snapshotId, Account snapshotOwner);
108109

110+
/**
111+
* Extracts the snapshot to a particular location.
112+
*
113+
* @param cmd
114+
* the command specifying url (where the snapshot needs to be extracted to), zoneId (zone where the snapshot exists) and
115+
* id (the id of the snapshot)
116+
*
117+
*/
118+
String extractSnapshot(ExtractSnapshotCmd cmd);
119+
109120
/**
110121
* Archives a snapshot from primary storage to secondary storage.
111122
* @param id Snapshot ID

api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,9 +345,11 @@ public interface ResponseGenerator {
345345

346346
SecurityGroupResponse createSecurityGroupResponse(SecurityGroup group);
347347

348-
ExtractResponse createExtractResponse(Long uploadId, Long id, Long zoneId, Long accountId, String mode, String url);
348+
ExtractResponse createImageExtractResponse(Long id, Long zoneId, Long accountId, String mode, String url);
349349

350-
ExtractResponse createExtractResponse(Long id, Long zoneId, Long accountId, String mode, String url);
350+
ExtractResponse createVolumeExtractResponse(Long id, Long zoneId, Long accountId, String mode, String url);
351+
352+
ExtractResponse createSnapshotExtractResponse(Long id, Long zoneId, Long accountId, String url);
351353

352354
String toSerializedString(CreateCmdResponse response, String responseType);
353355

api/src/main/java/org/apache/cloudstack/api/command/user/iso/ExtractIsoCmd.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ public void execute() {
120120
CallContext.current().setEventDetails(getEventDescription());
121121
String uploadUrl = _templateService.extract(this);
122122
if (uploadUrl != null) {
123-
ExtractResponse response = _responseGenerator.createExtractResponse(id, zoneId, getEntityOwnerId(), mode, uploadUrl);
123+
ExtractResponse response = _responseGenerator.createImageExtractResponse(id, zoneId, getEntityOwnerId(), mode, uploadUrl);
124124
response.setResponseName(getCommandName());
125125
response.setObjectName("iso");
126126
this.setResponseObject(response);
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.api.command.user.snapshot;
18+
19+
import com.cloud.event.EventTypes;
20+
import com.cloud.storage.Snapshot;
21+
import com.cloud.user.Account;
22+
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
23+
import org.apache.cloudstack.api.ACL;
24+
import org.apache.cloudstack.api.APICommand;
25+
import org.apache.cloudstack.api.ApiCommandResourceType;
26+
import org.apache.cloudstack.api.ApiConstants;
27+
import org.apache.cloudstack.api.ApiErrorCode;
28+
import org.apache.cloudstack.api.BaseAsyncCmd;
29+
import org.apache.cloudstack.api.Parameter;
30+
import org.apache.cloudstack.api.ServerApiException;
31+
import org.apache.cloudstack.api.response.ExtractResponse;
32+
import org.apache.cloudstack.api.response.SnapshotResponse;
33+
import org.apache.cloudstack.api.response.ZoneResponse;
34+
import org.apache.cloudstack.context.CallContext;
35+
36+
@APICommand(name = "extractSnapshot", description = "Returns a download URL for extracting a snapshot. It must be in the Backed Up state.", since = "4.20.0",
37+
responseObject = ExtractResponse.class, entityType = {Snapshot.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
38+
public class ExtractSnapshotCmd extends BaseAsyncCmd {
39+
40+
41+
/////////////////////////////////////////////////////
42+
//////////////// API parameters /////////////////////
43+
/////////////////////////////////////////////////////
44+
45+
@ACL(accessType = AccessType.OperateEntry)
46+
@Parameter(name=ApiConstants.ID, type=CommandType.UUID, entityType=SnapshotResponse.class, required=true, since="4.20.0", description="the ID of the snapshot")
47+
private Long id;
48+
49+
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, required = true, since="4.20.0",
50+
description = "the ID of the zone where the snapshot is located")
51+
private Long zoneId;
52+
53+
/////////////////////////////////////////////////////
54+
/////////////////// Accessors ///////////////////////
55+
/////////////////////////////////////////////////////
56+
57+
public Long getId() {
58+
return id;
59+
}
60+
61+
public Long getZoneId() {
62+
return zoneId;
63+
}
64+
65+
/////////////////////////////////////////////////////
66+
/////////////// API Implementation///////////////////
67+
/////////////////////////////////////////////////////
68+
69+
@Override
70+
public ApiCommandResourceType getApiResourceType() {
71+
return ApiCommandResourceType.Snapshot;
72+
}
73+
74+
@Override
75+
public Long getApiResourceId() {
76+
return getId();
77+
}
78+
79+
/**
80+
* @return ID of the snapshot to extract, if any. Otherwise returns the ACCOUNT_ID_SYSTEM, so ERROR events will be traceable.
81+
*/
82+
@Override
83+
public long getEntityOwnerId() {
84+
Snapshot snapshot = _entityMgr.findById(Snapshot.class, getId());
85+
if (snapshot != null) {
86+
return snapshot.getAccountId();
87+
}
88+
89+
return Account.ACCOUNT_ID_SYSTEM;
90+
}
91+
92+
@Override
93+
public String getEventType() {
94+
return EventTypes.EVENT_SNAPSHOT_EXTRACT;
95+
}
96+
97+
@Override
98+
public String getEventDescription() {
99+
return "Snapshot extraction job";
100+
}
101+
102+
@Override
103+
public void execute() {
104+
CallContext.current().setEventDetails("Snapshot ID: " + this._uuidMgr.getUuid(Snapshot.class, getId()));
105+
String uploadUrl = _snapshotService.extractSnapshot(this);
106+
logger.info("Extract URL [{}] of snapshot [{}].", uploadUrl, id);
107+
if (uploadUrl != null) {
108+
ExtractResponse response = _responseGenerator.createSnapshotExtractResponse(id, zoneId, getEntityOwnerId(), uploadUrl);
109+
response.setResponseName(getCommandName());
110+
this.setResponseObject(response);
111+
} else {
112+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to extract snapshot");
113+
}
114+
}
115+
}

api/src/main/java/org/apache/cloudstack/api/command/user/template/ExtractTemplateCmd.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,9 @@ public void execute() {
120120
CallContext.current().setEventDetails(getEventDescription());
121121
String uploadUrl = _templateService.extract(this);
122122
if (uploadUrl != null) {
123-
ExtractResponse response = _responseGenerator.createExtractResponse(id, zoneId, getEntityOwnerId(), mode, uploadUrl);
123+
ExtractResponse response = _responseGenerator.createImageExtractResponse(id, zoneId, getEntityOwnerId(), mode, uploadUrl);
124124
response.setResponseName(getCommandName());
125+
response.setObjectName("template");
125126
this.setResponseObject(response);
126127
} else {
127128
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to extract template");

api/src/main/java/org/apache/cloudstack/api/command/user/volume/ExtractVolumeCmd.java

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@
3131
import org.apache.cloudstack.api.response.ZoneResponse;
3232
import org.apache.cloudstack.context.CallContext;
3333

34-
import com.cloud.dc.DataCenter;
3534
import com.cloud.event.EventTypes;
36-
import com.cloud.storage.Upload;
3735
import com.cloud.storage.Volume;
3836
import com.cloud.user.Account;
3937

@@ -124,20 +122,8 @@ public void execute() {
124122
CallContext.current().setEventDetails("Volume Id: " + this._uuidMgr.getUuid(Volume.class, getId()));
125123
String uploadUrl = _volumeService.extractVolume(this);
126124
if (uploadUrl != null) {
127-
ExtractResponse response = new ExtractResponse();
125+
ExtractResponse response = _responseGenerator.createVolumeExtractResponse(id, zoneId, getEntityOwnerId(), mode, uploadUrl);
128126
response.setResponseName(getCommandName());
129-
response.setObjectName("volume");
130-
Volume vol = _entityMgr.findById(Volume.class, id);
131-
response.setId(vol.getUuid());
132-
response.setName(vol.getName());
133-
DataCenter zone = _entityMgr.findById(DataCenter.class, zoneId);
134-
response.setZoneId(zone.getUuid());
135-
response.setZoneName(zone.getName());
136-
response.setMode(mode);
137-
response.setState(Upload.Status.DOWNLOAD_URL_CREATED.toString());
138-
Account account = _entityMgr.findById(Account.class, getEntityOwnerId());
139-
response.setAccountId(account.getUuid());
140-
response.setUrl(uploadUrl);
141127
setResponseObject(response);
142128
} else {
143129
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to extract volume");

engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreVO.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ public class SnapshotDataStoreVO implements StateObject<ObjectInDataStoreStateMa
8686
@Column(name = "install_path")
8787
private String installPath;
8888

89+
@Column(name = "download_url", length = 2048)
90+
private String extractUrl;
91+
92+
@Column(name = "download_url_created")
93+
@Temporal(value = TemporalType.TIMESTAMP)
94+
private Date extractUrlCreated = null;
95+
8996
@Column(name = "update_count", updatable = true, nullable = false)
9097
protected long updatedCount;
9198

@@ -310,6 +317,22 @@ public void setVolumeId(Long volumeId) {
310317
this.volumeId = volumeId;
311318
}
312319

320+
public String getExtractUrl() {
321+
return extractUrl;
322+
}
323+
324+
public void setExtractUrl(String extractUrl) {
325+
this.extractUrl = extractUrl;
326+
}
327+
328+
public Date getExtractUrlCreated() {
329+
return extractUrlCreated;
330+
}
331+
332+
public void setExtractUrlCreated(Date extractUrlCreated) {
333+
this.extractUrlCreated = extractUrlCreated;
334+
}
335+
313336
public void setCreated(Date created) {
314337
this.created = created;
315338
}

engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_email_configuration`(
9393
-- Add `is_implicit` column to `host_tags` table
9494
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.host_tags', 'is_implicit', 'int(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT "If host tag is implicit or explicit" ');
9595

96+
-- Fields related to Snapshot Extraction
97+
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.snapshot_store_ref', 'download_url', 'varchar(2048) DEFAULT NULL');
98+
CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.snapshot_store_ref', 'download_url_created', 'datetime DEFAULT NULL');
99+
96100
-- Webhooks feature
97101
DROP TABLE IF EXISTS `cloud`.`webhook`;
98102
CREATE TABLE `cloud`.`webhook` (

0 commit comments

Comments
 (0)