Skip to content

Commit 252942f

Browse files
committed
Support Backup of Snapshots for Managed Storage
This PR adds an ability to Pass a new parameter, locationType, to the “createSnapshot” API command. Depending on the locationType, we decide where the snapshot should go in case of managed storage. There are two possible values for the locationType param 1) `Standard`: The standard operation for managed storage is to keep the snapshot on the device. For non-managed storage, this will be to upload it to secondary storage. This option will be the default. 2) `Archive`: Applicable only to managed storage. This will keep the snapshot on the secondary storage. For non-managed storage, this will result in an error. The reason for implementing this feature is to avoid a single point of failure for primary storage. Right now in case of managed storage, if the primary storage goes down, there is no easy way to recover data as all snapshots are also stored on the primary. This features allows us to mitigate that risk.
1 parent e4ba640 commit 252942f

File tree

34 files changed

+1472
-797
lines changed

34 files changed

+1472
-797
lines changed

api/src/com/cloud/storage/Snapshot.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,13 @@
1616
// under the License.
1717
package com.cloud.storage;
1818

19-
import java.util.Date;
20-
19+
import com.cloud.hypervisor.Hypervisor.HypervisorType;
20+
import com.cloud.utils.fsm.StateObject;
2121
import org.apache.cloudstack.acl.ControlledEntity;
2222
import org.apache.cloudstack.api.Identity;
2323
import org.apache.cloudstack.api.InternalIdentity;
2424

25-
import com.cloud.hypervisor.Hypervisor.HypervisorType;
26-
import com.cloud.utils.fsm.StateObject;
25+
import java.util.Date;
2726

2827
public interface Snapshot extends ControlledEntity, Identity, InternalIdentity, StateObject<Snapshot.State> {
2928
public enum Type {
@@ -67,6 +66,10 @@ enum Event {
6766
CreateRequested, OperationNotPerformed, BackupToSecondary, BackedupToSecondary, DestroyRequested, CopyingRequested, OperationSucceeded, OperationFailed
6867
}
6968

69+
enum LocationType {
70+
Standard, Archive
71+
}
72+
7073
public static final long MANUAL_POLICY_ID = 0L;
7174

7275
@Override
@@ -89,4 +92,5 @@ enum Event {
8992

9093
short getsnapshotType();
9194

95+
LocationType getLocationType(); // This type is in reference to the location where the snapshot resides (ex. primary storage, archive on secondary storage, etc.)
9296
}

api/src/com/cloud/storage/VolumeApiService.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,6 @@ public interface VolumeApiService {
7070
/**
7171
* Uploads the volume to secondary storage
7272
*
73-
* @param UploadVolumeCmdByAdmin cmd
74-
*
7573
* @return Volume object
7674
*/
7775
Volume uploadVolume(UploadVolumeCmd cmd) throws ResourceAllocationException;
@@ -82,11 +80,11 @@ public interface VolumeApiService {
8280

8381
Volume attachVolumeToVM(AttachVolumeCmd command);
8482

85-
Volume detachVolumeFromVM(DetachVolumeCmd cmmd);
83+
Volume detachVolumeFromVM(DetachVolumeCmd cmd);
8684

87-
Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm) throws ResourceAllocationException;
85+
Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType) throws ResourceAllocationException;
8886

89-
Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName) throws ResourceAllocationException;
87+
Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType) throws ResourceAllocationException;
9088

9189
Volume updateVolume(long volumeId, String path, String state, Long storageId, Boolean displayVolume, String customId, long owner, String chainInfo);
9290

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public interface SnapshotApiService {
8686

8787
boolean deleteSnapshotPolicies(DeleteSnapshotPoliciesCmd cmd);
8888

89-
Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName) throws ResourceAllocationException;
89+
Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType) throws ResourceAllocationException;
9090

9191
/**
9292
* Create a snapshot of a volume

api/src/org/apache/cloudstack/api/ApiConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ public class ApiConstants {
131131
public static final String INTERNAL_DNS1 = "internaldns1";
132132
public static final String INTERNAL_DNS2 = "internaldns2";
133133
public static final String INTERVAL_TYPE = "intervaltype";
134+
public static final String LOCATION_TYPE = "locationtype";
134135
public static final String IOPS_READ_RATE = "iopsreadrate";
135136
public static final String IOPS_WRITE_RATE = "iopswriterate";
136137
public static final String IP_ADDRESS = "ipaddress";

api/src/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@
1616
// under the License.
1717
package org.apache.cloudstack.api.command.user.snapshot;
1818

19-
import org.apache.log4j.Logger;
20-
19+
import com.cloud.event.EventTypes;
20+
import com.cloud.exception.InvalidParameterValueException;
21+
import com.cloud.exception.PermissionDeniedException;
22+
import com.cloud.exception.ResourceAllocationException;
23+
import com.cloud.projects.Project;
24+
import com.cloud.storage.Snapshot;
25+
import com.cloud.storage.Volume;
26+
import com.cloud.user.Account;
2127
import org.apache.cloudstack.api.APICommand;
2228
import org.apache.cloudstack.api.ApiCommandJobType;
2329
import org.apache.cloudstack.api.ApiConstants;
@@ -30,16 +36,7 @@
3036
import org.apache.cloudstack.api.response.SnapshotPolicyResponse;
3137
import org.apache.cloudstack.api.response.SnapshotResponse;
3238
import org.apache.cloudstack.api.response.VolumeResponse;
33-
import org.apache.cloudstack.context.CallContext;
34-
35-
import com.cloud.event.EventTypes;
36-
import com.cloud.exception.InvalidParameterValueException;
37-
import com.cloud.exception.PermissionDeniedException;
38-
import com.cloud.exception.ResourceAllocationException;
39-
import com.cloud.projects.Project;
40-
import com.cloud.storage.Snapshot;
41-
import com.cloud.storage.Volume;
42-
import com.cloud.user.Account;
39+
import org.apache.log4j.Logger;
4340

4441
@APICommand(name = "createSnapshot", description = "Creates an instant snapshot of a volume.", responseObject = SnapshotResponse.class, entityType = {Snapshot.class},
4542
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
@@ -74,6 +71,10 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd {
7471
@Parameter(name = ApiConstants.SNAPSHOT_QUIESCEVM, type = CommandType.BOOLEAN, required = false, description = "quiesce vm if true")
7572
private Boolean quiescevm;
7673

74+
@Parameter(name = ApiConstants.LOCATION_TYPE, type = CommandType.SHORT, required = false, description = "Currently applicable only for managed storage. " +
75+
"Valid location types: 1 = 'standard'; 2 = 'archive'. Default = 1.")
76+
private Short locationType;
77+
7778
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "the name of the snapshot")
7879
private String snapshotName;
7980

@@ -108,7 +109,7 @@ public String getSnapshotName() {
108109
}
109110

110111
public String getVolumeUuid() {
111-
Volume volume = (Volume)this._entityMgr.findById(Volume.class, getVolumeId());
112+
Volume volume = _entityMgr.findById(Volume.class, getVolumeId());
112113
if (volume == null) {
113114
throw new InvalidParameterValueException("Unable to find volume's UUID");
114115
}
@@ -184,7 +185,7 @@ public ApiCommandJobType getInstanceType() {
184185

185186
@Override
186187
public void create() throws ResourceAllocationException {
187-
Snapshot snapshot = _volumeService.allocSnapshot(getVolumeId(), getPolicyId(), getSnapshotName());
188+
Snapshot snapshot = _volumeService.allocSnapshot(getVolumeId(), getPolicyId(), getSnapshotName(), getLocationType());
188189
if (snapshot != null) {
189190
setEntityId(snapshot.getId());
190191
setEntityUuid(snapshot.getUuid());
@@ -195,24 +196,37 @@ public void create() throws ResourceAllocationException {
195196

196197
@Override
197198
public void execute() {
198-
s_logger.info("VOLSS: createSnapshotCmd starts:" + System.currentTimeMillis());
199-
CallContext.current().setEventDetails("Volume Id: " + getVolumeUuid());
200199
Snapshot snapshot;
201200
try {
202201
snapshot =
203-
_volumeService.takeSnapshot(getVolumeId(), getPolicyId(), getEntityId(), _accountService.getAccount(getEntityOwnerId()), getQuiescevm());
202+
_volumeService.takeSnapshot(getVolumeId(), getPolicyId(), getEntityId(), _accountService.getAccount(getEntityOwnerId()), getQuiescevm(), getLocationType());
203+
204204
if (snapshot != null) {
205205
SnapshotResponse response = _responseGenerator.createSnapshotResponse(snapshot);
206206
response.setResponseName(getCommandName());
207207
setResponseObject(response);
208208
} else {
209-
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create snapshot due to an internal error creating snapshot for volume " + volumeId);
209+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create snapshot due to an internal error creating snapshot for volume " + getVolumeId());
210210
}
211211
} catch (Exception e) {
212-
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create snapshot due to an internal error creating snapshot for volume " + volumeId);
212+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create snapshot due to an internal error creating snapshot for volume " + getVolumeId());
213213
}
214214
}
215215

216+
private Snapshot.LocationType getLocationType() {
217+
if (Snapshot.LocationType.values() == null || Snapshot.LocationType.values().length == 0) {
218+
return null;
219+
}
220+
221+
Snapshot.LocationType locationTypeToReturn = Snapshot.LocationType.values()[0];
222+
223+
if (locationType != null && locationType >= 1 && locationType <= Snapshot.LocationType.values().length) {
224+
locationTypeToReturn = Snapshot.LocationType.values()[locationType-1];
225+
}
226+
227+
return locationTypeToReturn;
228+
}
229+
216230
@Override
217231
public String getSyncObjType() {
218232
if (getSyncObjId() != null) {

api/src/org/apache/cloudstack/api/response/SnapshotResponse.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ public class SnapshotResponse extends BaseResponse implements ControlledEntityRe
8282
@Param(description = "valid types are hourly, daily, weekly, monthy, template, and none.")
8383
private String intervalType;
8484

85+
@SerializedName(ApiConstants.LOCATION_TYPE)
86+
@Param(description = "valid location types are primary and archive.")
87+
private String locationType;
88+
8589
@SerializedName(ApiConstants.STATE)
8690
@Param(description = "the state of the snapshot. BackedUp means that snapshot is ready to be used; Creating - the snapshot is being allocated on the primary storage; BackingUp - the snapshot is being backed up on secondary storage")
8791
private Snapshot.State state;
@@ -166,6 +170,10 @@ public void setIntervalType(String intervalType) {
166170
this.intervalType = intervalType;
167171
}
168172

173+
public void setLocationType(String locationType) {
174+
this.locationType = locationType;
175+
}
176+
169177
public void setState(Snapshot.State state) {
170178
this.state = state;
171179
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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.test;
18+
19+
import com.cloud.storage.Snapshot;
20+
import com.cloud.storage.VolumeApiService;
21+
import com.cloud.user.Account;
22+
import com.cloud.user.AccountService;
23+
import junit.framework.TestCase;
24+
import org.apache.cloudstack.api.ResponseGenerator;
25+
import org.apache.cloudstack.api.ServerApiException;
26+
import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotCmd;
27+
import org.apache.cloudstack.api.response.SnapshotResponse;
28+
import org.junit.Assert;
29+
import org.junit.Before;
30+
import org.junit.Ignore;
31+
import org.junit.Rule;
32+
import org.junit.Test;
33+
import org.junit.rules.ExpectedException;
34+
import org.mockito.Mockito;
35+
36+
import static org.mockito.Matchers.any;
37+
import static org.mockito.Matchers.anyBoolean;
38+
import static org.mockito.Matchers.anyLong;
39+
import static org.mockito.Matchers.anyString;
40+
import static org.mockito.Matchers.eq;
41+
42+
public class CreateSnapshotCmdTest extends TestCase {
43+
44+
private CreateSnapshotCmd createSnapshotCmd;
45+
private ResponseGenerator responseGenerator;
46+
47+
@Rule
48+
public ExpectedException expectedException = ExpectedException.none();
49+
50+
@Override
51+
@Before
52+
public void setUp() {
53+
54+
createSnapshotCmd = new CreateSnapshotCmd() {
55+
56+
@Override
57+
public String getCommandName() {
58+
return "createsnapshotresponse";
59+
}
60+
61+
@Override
62+
public Long getVolumeId(){
63+
return 1L;
64+
}
65+
66+
@Override
67+
public long getEntityOwnerId(){
68+
return 1L;
69+
}
70+
};
71+
72+
}
73+
74+
@Test
75+
public void testCreateSuccess() {
76+
77+
AccountService accountService = Mockito.mock(AccountService.class);
78+
Account account = Mockito.mock(Account.class);
79+
Mockito.when(accountService.getAccount(anyLong())).thenReturn(account);
80+
81+
VolumeApiService volumeApiService = Mockito.mock(VolumeApiService.class);
82+
Snapshot snapshot = Mockito.mock(Snapshot.class);
83+
try {
84+
85+
Mockito.when(volumeApiService.takeSnapshot(anyLong(), anyLong(), anyLong(),
86+
any(Account.class), anyBoolean(), eq(Snapshot.LocationType.Standard))).thenReturn(snapshot);
87+
88+
} catch (Exception e) {
89+
Assert.fail("Received exception when success expected " + e.getMessage());
90+
}
91+
92+
responseGenerator = Mockito.mock(ResponseGenerator.class);
93+
SnapshotResponse snapshotResponse = Mockito.mock(SnapshotResponse.class);
94+
Mockito.when(responseGenerator.createSnapshotResponse(snapshot)).thenReturn(snapshotResponse);
95+
Mockito.doNothing().when(snapshotResponse).setAccountName(anyString());
96+
97+
createSnapshotCmd._accountService = accountService;
98+
createSnapshotCmd._responseGenerator = responseGenerator;
99+
createSnapshotCmd._volumeService = volumeApiService;
100+
101+
createSnapshotCmd.execute();
102+
}
103+
104+
@Test
105+
@Ignore
106+
public void testCreateFailure() {
107+
108+
AccountService accountService = Mockito.mock(AccountService.class);
109+
Account account = Mockito.mock(Account.class);
110+
Mockito.when(accountService.getAccount(anyLong())).thenReturn(account);
111+
112+
VolumeApiService volumeApiService = Mockito.mock(VolumeApiService.class);
113+
114+
try {
115+
Mockito.when(volumeApiService.takeSnapshot(anyLong(), anyLong(), anyLong(),
116+
any(Account.class), anyBoolean(), eq(Snapshot.LocationType.Standard))).thenReturn(null);
117+
} catch (Exception e) {
118+
Assert.fail("Received exception when success expected " + e.getMessage());
119+
}
120+
121+
createSnapshotCmd._accountService = accountService;
122+
createSnapshotCmd._volumeService = volumeApiService;
123+
124+
try {
125+
createSnapshotCmd.execute();
126+
} catch (ServerApiException exception) {
127+
Assert.assertEquals("Failed to create snapshot due to an internal error creating snapshot for volume 1", exception.getDescription());
128+
}
129+
}
130+
}

core/src/org/apache/cloudstack/storage/to/PrimaryDataStoreTO.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,12 @@
1919

2020
package org.apache.cloudstack.storage.to;
2121

22-
import java.util.Map;
23-
24-
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
25-
2622
import com.cloud.agent.api.to.DataStoreTO;
2723
import com.cloud.storage.DataStoreRole;
2824
import com.cloud.storage.Storage.StoragePoolType;
25+
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore;
26+
27+
import java.util.Map;
2928

3029
public class PrimaryDataStoreTO implements DataStoreTO {
3130
public static final String MANAGED = PrimaryDataStore.MANAGED;
@@ -51,6 +50,7 @@ public class PrimaryDataStoreTO implements DataStoreTO {
5150
private final String url;
5251
private Map<String, String> details;
5352
private static final String pathSeparator = "/";
53+
private boolean isManaged;
5454

5555
public PrimaryDataStoreTO(PrimaryDataStore dataStore) {
5656
this.uuid = dataStore.getUuid();
@@ -62,6 +62,7 @@ public PrimaryDataStoreTO(PrimaryDataStore dataStore) {
6262
this.setPort(dataStore.getPort());
6363
this.url = dataStore.getUri();
6464
this.details = dataStore.getDetails();
65+
this.isManaged = dataStore.isManaged();
6566
}
6667

6768
public long getId() {
@@ -144,4 +145,8 @@ public String toString() {
144145
.append("]")
145146
.toString();
146147
}
148+
149+
public boolean isManaged() {
150+
return isManaged;
151+
}
147152
}

0 commit comments

Comments
 (0)