Skip to content

Commit c462be1

Browse files
New API "checkVolume" to check and repair any leaks or issues reported by qemu-img check (#8577)
* Introduced a new API checkVolumeAndRepair that allows users or admins to check and repair if any leaks observed. Currently this is supported only for KVM * some fixes * Added unit tests * addressed review comments * add repair volume while granting access * Changed repair parameter to accept both leaks/all * Introduced new global setting volume.check.and.repair.before.use to do volume check and repair before VM start or volume attach operations * Added volume check and repair changes only during VM start and volume attach operations * Refactored the names to look similar across the code * Some code fixes * remove unused code * Renamed repair values * Fixed unit tests * changed version * Address review comments * Code refactored * used volume name in logs * Changed the API to Async and the setting scope to storage pool * Fixed exit value handling with check volume command * Fixed storage scope to the setting * Fix volume format issues * Refactored the log messages * Fix formatting
1 parent 45d267c commit c462be1

File tree

23 files changed

+1429
-9
lines changed

23 files changed

+1429
-9
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ public class EventTypes {
303303
public static final String EVENT_VOLUME_CREATE = "VOLUME.CREATE";
304304
public static final String EVENT_VOLUME_DELETE = "VOLUME.DELETE";
305305
public static final String EVENT_VOLUME_ATTACH = "VOLUME.ATTACH";
306+
public static final String EVENT_VOLUME_CHECK = "VOLUME.CHECK";
306307
public static final String EVENT_VOLUME_DETACH = "VOLUME.DETACH";
307308
public static final String EVENT_VOLUME_EXTRACT = "VOLUME.EXTRACT";
308309
public static final String EVENT_VOLUME_UPLOAD = "VOLUME.UPLOAD";

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
import java.util.List;
2323
import java.util.Map;
2424

25+
import com.cloud.utils.Pair;
2526
import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
2627
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
2728
import org.apache.cloudstack.api.command.user.volume.ChangeOfferingForVolumeCmd;
29+
import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd;
2830
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
2931
import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
3032
import org.apache.cloudstack.api.command.user.volume.ExtractVolumeCmd;
@@ -178,4 +180,6 @@ Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account acc
178180
void publishVolumeCreationUsageEvent(Volume volume);
179181

180182
boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitionException;
183+
184+
Pair<String, String> checkAndRepairVolume(CheckAndRepairVolumeCmd cmd) throws ResourceAllocationException;
181185
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ public class ApiConstants {
378378
public static final String RECEIVED_BYTES = "receivedbytes";
379379
public static final String RECONNECT = "reconnect";
380380
public static final String RECOVER = "recover";
381+
public static final String REPAIR = "repair";
381382
public static final String REQUIRES_HVM = "requireshvm";
382383
public static final String RESOURCE_NAME = "resourcename";
383384
public static final String RESOURCE_TYPE = "resourcetype";
@@ -501,6 +502,9 @@ public class ApiConstants {
501502
public static final String IS_VOLATILE = "isvolatile";
502503
public static final String VOLUME_ID = "volumeid";
503504
public static final String VOLUMES = "volumes";
505+
public static final String VOLUME_CHECK_RESULT = "volumecheckresult";
506+
public static final String VOLUME_REPAIR_RESULT = "volumerepairresult";
507+
504508
public static final String ZONE = "zone";
505509
public static final String ZONE_ID = "zoneid";
506510
public static final String ZONE_NAME = "zonename";
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
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.volume;
18+
19+
import com.cloud.event.EventTypes;
20+
import com.cloud.exception.InvalidParameterValueException;
21+
import org.apache.cloudstack.acl.RoleType;
22+
import org.apache.cloudstack.api.APICommand;
23+
import org.apache.cloudstack.api.ApiCommandResourceType;
24+
import org.apache.cloudstack.api.ApiConstants;
25+
import org.apache.cloudstack.api.ApiErrorCode;
26+
import org.apache.cloudstack.api.BaseAsyncCmd;
27+
import org.apache.cloudstack.api.Parameter;
28+
import org.apache.cloudstack.api.ResponseObject.ResponseView;
29+
import org.apache.cloudstack.api.ServerApiException;
30+
import org.apache.cloudstack.api.response.VolumeResponse;
31+
import org.apache.cloudstack.context.CallContext;
32+
import org.apache.log4j.Logger;
33+
34+
import com.cloud.exception.ResourceAllocationException;
35+
import com.cloud.storage.Volume;
36+
import com.cloud.user.Account;
37+
import com.cloud.utils.Pair;
38+
import com.cloud.utils.StringUtils;
39+
40+
import java.util.Arrays;
41+
42+
@APICommand(name = "checkVolume", description = "Check the volume for any errors or leaks and also repairs when repair parameter is passed, this is currently supported for KVM only", responseObject = VolumeResponse.class, entityType = {Volume.class},
43+
since = "4.19.1",
44+
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
45+
public class CheckAndRepairVolumeCmd extends BaseAsyncCmd {
46+
public static final Logger s_logger = Logger.getLogger(CheckAndRepairVolumeCmd.class.getName());
47+
48+
private static final String s_name = "checkandrepairvolumeresponse";
49+
50+
/////////////////////////////////////////////////////
51+
//////////////// API parameters /////////////////////
52+
/////////////////////////////////////////////////////
53+
54+
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = VolumeResponse.class, required = true, description = "The ID of the volume")
55+
private Long id;
56+
57+
@Parameter(name = ApiConstants.REPAIR, type = CommandType.STRING, required = false, description = "parameter to repair the volume, leaks or all are the possible values")
58+
private String repair;
59+
60+
/////////////////////////////////////////////////////
61+
/////////////////// Accessors ///////////////////////
62+
/////////////////////////////////////////////////////
63+
64+
public enum RepairValues {
65+
LEAKS, ALL
66+
}
67+
68+
public Long getId() {
69+
return id;
70+
}
71+
72+
public String getRepair() {
73+
if (org.apache.commons.lang3.StringUtils.isNotEmpty(repair)) {
74+
RepairValues repairType = Enum.valueOf(RepairValues.class, repair.toUpperCase());
75+
if (repairType == null) {
76+
throw new InvalidParameterValueException(String.format("Repair parameter can only take the following values: %s" + Arrays.toString(RepairValues.values())));
77+
}
78+
return repair.toLowerCase();
79+
}
80+
return null;
81+
}
82+
83+
/////////////////////////////////////////////////////
84+
/////////////// API Implementation///////////////////
85+
/////////////////////////////////////////////////////
86+
87+
@Override
88+
public String getCommandName() {
89+
return s_name;
90+
}
91+
92+
@Override
93+
public long getEntityOwnerId() {
94+
Volume volume = _entityMgr.findById(Volume.class, getId());
95+
if (volume != null) {
96+
return volume.getAccountId();
97+
}
98+
99+
return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked
100+
}
101+
102+
@Override
103+
public String getEventType() {
104+
return EventTypes.EVENT_VOLUME_CHECK;
105+
}
106+
107+
@Override
108+
public String getEventDescription() {
109+
return String.format("check and repair operation on volume: %s", this._uuidMgr.getUuid(Volume.class, getId()));
110+
}
111+
112+
@Override
113+
public Long getApiResourceId() {
114+
return id;
115+
}
116+
117+
@Override
118+
public ApiCommandResourceType getApiResourceType() {
119+
return ApiCommandResourceType.Volume;
120+
}
121+
122+
@Override
123+
public void execute() throws ResourceAllocationException {
124+
CallContext.current().setEventDetails("Volume Id: " + getId());
125+
Pair<String, String> result = _volumeService.checkAndRepairVolume(this);
126+
Volume volume = _responseGenerator.findVolumeById(getId());
127+
if (result != null) {
128+
VolumeResponse response = _responseGenerator.createVolumeResponse(ResponseView.Full, volume);
129+
response.setVolumeCheckResult(StringUtils.parseJsonToMap(result.first()));
130+
if (getRepair() != null) {
131+
response.setVolumeRepairResult(StringUtils.parseJsonToMap(result.second()));
132+
}
133+
response.setResponseName(getCommandName());
134+
setResponseObject(response);
135+
} else {
136+
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to check volume and repair");
137+
}
138+
}
139+
}

api/src/main/java/org/apache/cloudstack/api/response/VolumeResponse.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.Date;
2020
import java.util.LinkedHashSet;
21+
import java.util.Map;
2122
import java.util.Set;
2223

2324
import org.apache.cloudstack.acl.RoleType;
@@ -288,6 +289,14 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
288289
@Param(description = "volume uuid that is given by virtualisation provider (only for VMware)")
289290
private String externalUuid;
290291

292+
@SerializedName(ApiConstants.VOLUME_CHECK_RESULT)
293+
@Param(description = "details for the volume check result, they may vary for different hypervisors, since = 4.19.1")
294+
private Map<String, String> volumeCheckResult;
295+
296+
@SerializedName(ApiConstants.VOLUME_REPAIR_RESULT)
297+
@Param(description = "details for the volume repair result, they may vary for different hypervisors, since = 4.19.1")
298+
private Map<String, String> volumeRepairResult;
299+
291300
public String getPath() {
292301
return path;
293302
}
@@ -817,4 +826,20 @@ public String getExternalUuid() {
817826
public void setExternalUuid(String externalUuid) {
818827
this.externalUuid = externalUuid;
819828
}
829+
830+
public Map<String, String> getVolumeCheckResult() {
831+
return volumeCheckResult;
832+
}
833+
834+
public void setVolumeCheckResult(Map<String, String> volumeCheckResult) {
835+
this.volumeCheckResult = volumeCheckResult;
836+
}
837+
838+
public Map<String, String> getVolumeRepairResult() {
839+
return volumeRepairResult;
840+
}
841+
842+
public void setVolumeRepairResult(Map<String, String> volumeRepairResult) {
843+
this.volumeRepairResult = volumeRepairResult;
844+
}
820845
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
20+
package com.cloud.agent.api.storage;
21+
22+
import com.cloud.agent.api.Answer;
23+
24+
public class CheckAndRepairVolumeAnswer extends Answer {
25+
private String volumeCheckExecutionResult;
26+
private String volumeRepairExecutionResult;
27+
28+
protected CheckAndRepairVolumeAnswer() {
29+
super();
30+
}
31+
32+
public CheckAndRepairVolumeAnswer(CheckAndRepairVolumeCommand cmd, boolean result, String details, String volumeCheckExecutionResult, String volumeRepairedExecutionResult) {
33+
super(cmd, result, details);
34+
this.volumeCheckExecutionResult = volumeCheckExecutionResult;
35+
this.volumeRepairExecutionResult = volumeRepairedExecutionResult;
36+
}
37+
38+
public CheckAndRepairVolumeAnswer(CheckAndRepairVolumeCommand cmd, boolean result, String details) {
39+
super(cmd, result, details);
40+
}
41+
42+
public String getVolumeCheckExecutionResult() {
43+
return volumeCheckExecutionResult;
44+
}
45+
46+
public String getVolumeRepairExecutionResult() {
47+
return volumeRepairExecutionResult;
48+
}
49+
50+
public void setVolumeCheckExecutionResult(String volumeCheckExecutionResult) {
51+
this.volumeCheckExecutionResult = volumeCheckExecutionResult;
52+
}
53+
54+
public void setVolumeRepairExecutionResult(String volumeRepairExecutionResult) {
55+
this.volumeRepairExecutionResult = volumeRepairExecutionResult;
56+
}
57+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
20+
package com.cloud.agent.api.storage;
21+
22+
import com.cloud.agent.api.Command;
23+
import com.cloud.agent.api.LogLevel;
24+
import com.cloud.agent.api.to.StorageFilerTO;
25+
26+
import java.util.Arrays;
27+
28+
public class CheckAndRepairVolumeCommand extends Command {
29+
private String path;
30+
private StorageFilerTO pool;
31+
private String repair;
32+
@LogLevel(LogLevel.Log4jLevel.Off)
33+
private byte[] passphrase;
34+
private String encryptFormat;
35+
36+
public CheckAndRepairVolumeCommand(String path, StorageFilerTO pool, String repair, byte[] passphrase, String encryptFormat) {
37+
this.path = path;
38+
this.pool = pool;
39+
this.repair = repair;
40+
this.passphrase = passphrase;
41+
this.encryptFormat = encryptFormat;
42+
}
43+
44+
public String getPath() {
45+
return path;
46+
}
47+
48+
public String getPoolUuid() {
49+
return pool.getUuid();
50+
}
51+
52+
public StorageFilerTO getPool() {
53+
return pool;
54+
}
55+
56+
public String getRepair() {
57+
return repair;
58+
}
59+
60+
public String getEncryptFormat() { return encryptFormat; }
61+
62+
public byte[] getPassphrase() { return passphrase; }
63+
64+
public void clearPassphrase() {
65+
if (this.passphrase != null) {
66+
Arrays.fill(this.passphrase, (byte) 0);
67+
}
68+
}
69+
70+
/**
71+
* {@inheritDoc}
72+
*/
73+
@Override
74+
public boolean executeInSequence() {
75+
return false;
76+
}
77+
}

engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,8 @@ boolean copyPoliciesBetweenVolumesAndDestroySourceVolumeAfterMigration(ObjectInD
117117
VolumeInfo sourceVolume, VolumeInfo destinationVolume, boolean retryExpungeVolumeAsync);
118118

119119
void moveVolumeOnSecondaryStorageToAnotherAccount(Volume volume, Account sourceAccount, Account destAccount);
120+
121+
Pair<String, String> checkAndRepairVolume(VolumeInfo volume);
122+
123+
void checkAndRepairVolumeBasedOnConfig(DataObject dataObject, Host host);
120124
}

0 commit comments

Comments
 (0)