Skip to content

Commit bfad68e

Browse files
syedMike Tutkowski
authored andcommitted
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 a509790 commit bfad68e

File tree

36 files changed

+1529
-815
lines changed

36 files changed

+1529
-815
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+
PRIMARY, SECONDARY
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: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,15 @@
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;
27+
import com.cloud.utils.exception.CloudRuntimeException;
2128
import org.apache.cloudstack.api.APICommand;
2229
import org.apache.cloudstack.api.ApiCommandJobType;
2330
import org.apache.cloudstack.api.ApiConstants;
@@ -30,16 +37,7 @@
3037
import org.apache.cloudstack.api.response.SnapshotPolicyResponse;
3138
import org.apache.cloudstack.api.response.SnapshotResponse;
3239
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;
40+
import org.apache.log4j.Logger;
4341

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

75+
@Parameter(name = ApiConstants.LOCATION_TYPE, type = CommandType.STRING, required = false, description = "Currently applicable only for managed storage. " +
76+
"Valid location types: 'primary', 'secondary'. Default = 'primary'.")
77+
private String locationType;
78+
7779
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "the name of the snapshot")
7880
private String snapshotName;
7981

@@ -108,7 +110,7 @@ public String getSnapshotName() {
108110
}
109111

110112
public String getVolumeUuid() {
111-
Volume volume = (Volume)this._entityMgr.findById(Volume.class, getVolumeId());
113+
Volume volume = _entityMgr.findById(Volume.class, getVolumeId());
112114
if (volume == null) {
113115
throw new InvalidParameterValueException("Unable to find volume's UUID");
114116
}
@@ -184,7 +186,7 @@ public ApiCommandJobType getInstanceType() {
184186

185187
@Override
186188
public void create() throws ResourceAllocationException {
187-
Snapshot snapshot = _volumeService.allocSnapshot(getVolumeId(), getPolicyId(), getSnapshotName());
189+
Snapshot snapshot = _volumeService.allocSnapshot(getVolumeId(), getPolicyId(), getSnapshotName(), getLocationType());
188190
if (snapshot != null) {
189191
setEntityId(snapshot.getId());
190192
setEntityUuid(snapshot.getUuid());
@@ -195,21 +197,37 @@ public void create() throws ResourceAllocationException {
195197

196198
@Override
197199
public void execute() {
198-
s_logger.info("VOLSS: createSnapshotCmd starts:" + System.currentTimeMillis());
199-
CallContext.current().setEventDetails("Volume Id: " + getVolumeUuid());
200200
Snapshot snapshot;
201201
try {
202202
snapshot =
203-
_volumeService.takeSnapshot(getVolumeId(), getPolicyId(), getEntityId(), _accountService.getAccount(getEntityOwnerId()), getQuiescevm());
203+
_volumeService.takeSnapshot(getVolumeId(), getPolicyId(), getEntityId(), _accountService.getAccount(getEntityOwnerId()), getQuiescevm(), getLocationType());
204+
204205
if (snapshot != null) {
205206
SnapshotResponse response = _responseGenerator.createSnapshotResponse(snapshot);
206207
response.setResponseName(getCommandName());
207208
setResponseObject(response);
208209
} else {
209-
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create snapshot due to an internal error creating snapshot for volume " + volumeId);
210+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create snapshot due to an internal error creating snapshot for volume " + getVolumeId());
210211
}
211212
} catch (Exception e) {
212-
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create snapshot due to an internal error creating snapshot for volume " + volumeId);
213+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create snapshot due to an internal error creating snapshot for volume " + getVolumeId());
214+
}
215+
}
216+
217+
private Snapshot.LocationType getLocationType() {
218+
219+
if (Snapshot.LocationType.values() == null || Snapshot.LocationType.values().length == 0 || locationType == null) {
220+
return null;
221+
}
222+
223+
try {
224+
String lType = locationType.trim().toUpperCase();
225+
return Snapshot.LocationType.valueOf(lType);
226+
} catch (IllegalArgumentException e) {
227+
String errMesg = "Invalid locationType " + locationType + "Specified for volume " + getVolumeId()
228+
+ " Valid values are: primary,secondary ";
229+
s_logger.warn(errMesg);
230+
throw new CloudRuntimeException(errMesg);
213231
}
214232
}
215233

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,15 @@
1616
// under the License.
1717
package org.apache.cloudstack.api.response;
1818

19-
import java.util.Date;
20-
import java.util.List;
21-
19+
import com.cloud.serializer.Param;
20+
import com.cloud.storage.Snapshot;
2221
import com.google.gson.annotations.SerializedName;
23-
2422
import org.apache.cloudstack.api.ApiConstants;
2523
import org.apache.cloudstack.api.BaseResponse;
2624
import org.apache.cloudstack.api.EntityReference;
2725

28-
import com.cloud.serializer.Param;
29-
import com.cloud.storage.Snapshot;
26+
import java.util.Date;
27+
import java.util.List;
3028

3129
@EntityReference(value = Snapshot.class)
3230
public class SnapshotResponse extends BaseResponse implements ControlledEntityResponse {
@@ -82,6 +80,10 @@ public class SnapshotResponse extends BaseResponse implements ControlledEntityRe
8280
@Param(description = "valid types are hourly, daily, weekly, monthy, template, and none.")
8381
private String intervalType;
8482

83+
@SerializedName(ApiConstants.LOCATION_TYPE)
84+
@Param(description = "valid location types are primary and secondary.")
85+
private String locationType;
86+
8587
@SerializedName(ApiConstants.STATE)
8688
@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")
8789
private Snapshot.State state;
@@ -166,6 +168,10 @@ public void setIntervalType(String intervalType) {
166168
this.intervalType = intervalType;
167169
}
168170

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

0 commit comments

Comments
 (0)