mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Merge remote-tracking branch 'apache/4.19'
This commit is contained in:
commit
b29ec2bf12
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -33,7 +33,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -211,7 +211,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
|
||||
2
.github/workflows/codecov.yml
vendored
2
.github/workflows/codecov.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK11
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '11'
|
||||
|
||||
2
.github/workflows/main-sonar-check.yml
vendored
2
.github/workflows/main-sonar-check.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK11
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '11'
|
||||
|
||||
2
.github/workflows/rat.yml
vendored
2
.github/workflows/rat.yml
vendored
@ -32,7 +32,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
|
||||
2
.github/workflows/sonar-check.yml
vendored
2
.github/workflows/sonar-check.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up JDK11
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '11'
|
||||
|
||||
@ -303,6 +303,7 @@ public class EventTypes {
|
||||
public static final String EVENT_VOLUME_CREATE = "VOLUME.CREATE";
|
||||
public static final String EVENT_VOLUME_DELETE = "VOLUME.DELETE";
|
||||
public static final String EVENT_VOLUME_ATTACH = "VOLUME.ATTACH";
|
||||
public static final String EVENT_VOLUME_CHECK = "VOLUME.CHECK";
|
||||
public static final String EVENT_VOLUME_DETACH = "VOLUME.DETACH";
|
||||
public static final String EVENT_VOLUME_EXTRACT = "VOLUME.EXTRACT";
|
||||
public static final String EVENT_VOLUME_UPLOAD = "VOLUME.UPLOAD";
|
||||
|
||||
@ -22,9 +22,11 @@ import java.net.MalformedURLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.utils.Pair;
|
||||
import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.ChangeOfferingForVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.ExtractVolumeCmd;
|
||||
@ -178,4 +180,6 @@ public interface VolumeApiService {
|
||||
void publishVolumeCreationUsageEvent(Volume volume);
|
||||
|
||||
boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitionException;
|
||||
|
||||
Pair<String, String> checkAndRepairVolume(CheckAndRepairVolumeCmd cmd) throws ResourceAllocationException;
|
||||
}
|
||||
|
||||
@ -380,6 +380,7 @@ public class ApiConstants {
|
||||
public static final String RECEIVED_BYTES = "receivedbytes";
|
||||
public static final String RECONNECT = "reconnect";
|
||||
public static final String RECOVER = "recover";
|
||||
public static final String REPAIR = "repair";
|
||||
public static final String REQUIRES_HVM = "requireshvm";
|
||||
public static final String RESOURCE_COUNT = "resourcecount";
|
||||
public static final String RESOURCE_NAME = "resourcename";
|
||||
@ -506,6 +507,9 @@ public class ApiConstants {
|
||||
public static final String IS_VOLATILE = "isvolatile";
|
||||
public static final String VOLUME_ID = "volumeid";
|
||||
public static final String VOLUMES = "volumes";
|
||||
public static final String VOLUME_CHECK_RESULT = "volumecheckresult";
|
||||
public static final String VOLUME_REPAIR_RESULT = "volumerepairresult";
|
||||
|
||||
public static final String ZONE = "zone";
|
||||
public static final String ZONE_ID = "zoneid";
|
||||
public static final String ZONE_NAME = "zonename";
|
||||
|
||||
@ -0,0 +1,139 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you 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 org.apache.cloudstack.api.command.user.volume;
|
||||
|
||||
import com.cloud.event.EventTypes;
|
||||
import com.cloud.exception.InvalidParameterValueException;
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseAsyncCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ResponseObject.ResponseView;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.VolumeResponse;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.exception.ResourceAllocationException;
|
||||
import com.cloud.storage.Volume;
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.StringUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@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},
|
||||
since = "4.19.1",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
|
||||
public class CheckAndRepairVolumeCmd extends BaseAsyncCmd {
|
||||
public static final Logger s_logger = Logger.getLogger(CheckAndRepairVolumeCmd.class.getName());
|
||||
|
||||
private static final String s_name = "checkandrepairvolumeresponse";
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = VolumeResponse.class, required = true, description = "The ID of the volume")
|
||||
private Long id;
|
||||
|
||||
@Parameter(name = ApiConstants.REPAIR, type = CommandType.STRING, required = false, description = "parameter to repair the volume, leaks or all are the possible values")
|
||||
private String repair;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public enum RepairValues {
|
||||
LEAKS, ALL
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getRepair() {
|
||||
if (org.apache.commons.lang3.StringUtils.isNotEmpty(repair)) {
|
||||
RepairValues repairType = Enum.valueOf(RepairValues.class, repair.toUpperCase());
|
||||
if (repairType == null) {
|
||||
throw new InvalidParameterValueException(String.format("Repair parameter can only take the following values: %s" + Arrays.toString(RepairValues.values())));
|
||||
}
|
||||
return repair.toLowerCase();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return s_name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
Volume volume = _entityMgr.findById(Volume.class, getId());
|
||||
if (volume != null) {
|
||||
return volume.getAccountId();
|
||||
}
|
||||
|
||||
return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType() {
|
||||
return EventTypes.EVENT_VOLUME_CHECK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventDescription() {
|
||||
return String.format("check and repair operation on volume: %s", this._uuidMgr.getUuid(Volume.class, getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getApiResourceId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandResourceType getApiResourceType() {
|
||||
return ApiCommandResourceType.Volume;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws ResourceAllocationException {
|
||||
CallContext.current().setEventDetails("Volume Id: " + getId());
|
||||
Pair<String, String> result = _volumeService.checkAndRepairVolume(this);
|
||||
Volume volume = _responseGenerator.findVolumeById(getId());
|
||||
if (result != null) {
|
||||
VolumeResponse response = _responseGenerator.createVolumeResponse(ResponseView.Full, volume);
|
||||
response.setVolumeCheckResult(StringUtils.parseJsonToMap(result.first()));
|
||||
if (getRepair() != null) {
|
||||
response.setVolumeRepairResult(StringUtils.parseJsonToMap(result.second()));
|
||||
}
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
} else {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to check volume and repair");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,7 @@ package org.apache.cloudstack.api.response;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
@ -288,6 +289,14 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
|
||||
@Param(description = "volume uuid that is given by virtualisation provider (only for VMware)")
|
||||
private String externalUuid;
|
||||
|
||||
@SerializedName(ApiConstants.VOLUME_CHECK_RESULT)
|
||||
@Param(description = "details for the volume check result, they may vary for different hypervisors, since = 4.19.1")
|
||||
private Map<String, String> volumeCheckResult;
|
||||
|
||||
@SerializedName(ApiConstants.VOLUME_REPAIR_RESULT)
|
||||
@Param(description = "details for the volume repair result, they may vary for different hypervisors, since = 4.19.1")
|
||||
private Map<String, String> volumeRepairResult;
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
@ -817,4 +826,20 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
|
||||
public void setExternalUuid(String externalUuid) {
|
||||
this.externalUuid = externalUuid;
|
||||
}
|
||||
|
||||
public Map<String, String> getVolumeCheckResult() {
|
||||
return volumeCheckResult;
|
||||
}
|
||||
|
||||
public void setVolumeCheckResult(Map<String, String> volumeCheckResult) {
|
||||
this.volumeCheckResult = volumeCheckResult;
|
||||
}
|
||||
|
||||
public Map<String, String> getVolumeRepairResult() {
|
||||
return volumeRepairResult;
|
||||
}
|
||||
|
||||
public void setVolumeRepairResult(Map<String, String> volumeRepairResult) {
|
||||
this.volumeRepairResult = volumeRepairResult;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you 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 com.cloud.agent.api.storage;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
|
||||
public class CheckAndRepairVolumeAnswer extends Answer {
|
||||
private String volumeCheckExecutionResult;
|
||||
private String volumeRepairExecutionResult;
|
||||
|
||||
protected CheckAndRepairVolumeAnswer() {
|
||||
super();
|
||||
}
|
||||
|
||||
public CheckAndRepairVolumeAnswer(CheckAndRepairVolumeCommand cmd, boolean result, String details, String volumeCheckExecutionResult, String volumeRepairedExecutionResult) {
|
||||
super(cmd, result, details);
|
||||
this.volumeCheckExecutionResult = volumeCheckExecutionResult;
|
||||
this.volumeRepairExecutionResult = volumeRepairedExecutionResult;
|
||||
}
|
||||
|
||||
public CheckAndRepairVolumeAnswer(CheckAndRepairVolumeCommand cmd, boolean result, String details) {
|
||||
super(cmd, result, details);
|
||||
}
|
||||
|
||||
public String getVolumeCheckExecutionResult() {
|
||||
return volumeCheckExecutionResult;
|
||||
}
|
||||
|
||||
public String getVolumeRepairExecutionResult() {
|
||||
return volumeRepairExecutionResult;
|
||||
}
|
||||
|
||||
public void setVolumeCheckExecutionResult(String volumeCheckExecutionResult) {
|
||||
this.volumeCheckExecutionResult = volumeCheckExecutionResult;
|
||||
}
|
||||
|
||||
public void setVolumeRepairExecutionResult(String volumeRepairExecutionResult) {
|
||||
this.volumeRepairExecutionResult = volumeRepairExecutionResult;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you 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 com.cloud.agent.api.storage;
|
||||
|
||||
import com.cloud.agent.api.Command;
|
||||
import com.cloud.agent.api.LogLevel;
|
||||
import com.cloud.agent.api.to.StorageFilerTO;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class CheckAndRepairVolumeCommand extends Command {
|
||||
private String path;
|
||||
private StorageFilerTO pool;
|
||||
private String repair;
|
||||
@LogLevel(LogLevel.Log4jLevel.Off)
|
||||
private byte[] passphrase;
|
||||
private String encryptFormat;
|
||||
|
||||
public CheckAndRepairVolumeCommand(String path, StorageFilerTO pool, String repair, byte[] passphrase, String encryptFormat) {
|
||||
this.path = path;
|
||||
this.pool = pool;
|
||||
this.repair = repair;
|
||||
this.passphrase = passphrase;
|
||||
this.encryptFormat = encryptFormat;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public String getPoolUuid() {
|
||||
return pool.getUuid();
|
||||
}
|
||||
|
||||
public StorageFilerTO getPool() {
|
||||
return pool;
|
||||
}
|
||||
|
||||
public String getRepair() {
|
||||
return repair;
|
||||
}
|
||||
|
||||
public String getEncryptFormat() { return encryptFormat; }
|
||||
|
||||
public byte[] getPassphrase() { return passphrase; }
|
||||
|
||||
public void clearPassphrase() {
|
||||
if (this.passphrase != null) {
|
||||
Arrays.fill(this.passphrase, (byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean executeInSequence() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -117,4 +117,8 @@ public interface VolumeService {
|
||||
VolumeInfo sourceVolume, VolumeInfo destinationVolume, boolean retryExpungeVolumeAsync);
|
||||
|
||||
void moveVolumeOnSecondaryStorageToAnotherAccount(Volume volume, Account sourceAccount, Account destAccount);
|
||||
|
||||
Pair<String, String> checkAndRepairVolume(VolumeInfo volume);
|
||||
|
||||
void checkAndRepairVolumeBasedOnConfig(DataObject dataObject, Host host);
|
||||
}
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you 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 com.cloud.vm;
|
||||
|
||||
public class VmWorkCheckAndRepairVolume extends VmWork {
|
||||
|
||||
private static final long serialVersionUID = 341816293003023824L;
|
||||
|
||||
private Long volumeId;
|
||||
|
||||
private String repair;
|
||||
|
||||
public VmWorkCheckAndRepairVolume(long userId, long accountId, long vmId, String handlerName,
|
||||
Long volumeId, String repair) {
|
||||
super(userId, accountId, vmId, handlerName);
|
||||
this.repair = repair;
|
||||
this.volumeId = volumeId;
|
||||
}
|
||||
|
||||
public Long getVolumeId() {
|
||||
return volumeId;
|
||||
}
|
||||
|
||||
public String getRepair() {
|
||||
return repair;
|
||||
}
|
||||
}
|
||||
@ -1917,6 +1917,8 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handleCheckAndRepairVolume(vol, vm.getVirtualMachine().getHostId());
|
||||
}
|
||||
} else if (task.type == VolumeTaskType.MIGRATE) {
|
||||
pool = (StoragePool)dataStoreMgr.getDataStore(task.pool.getId(), DataStoreRole.Primary);
|
||||
@ -1959,6 +1961,16 @@ public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrati
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCheckAndRepairVolume(Volume vol, Long hostId) {
|
||||
Host host = _hostDao.findById(hostId);
|
||||
try {
|
||||
volService.checkAndRepairVolumeBasedOnConfig(volFactory.getVolume(vol.getId()), host);
|
||||
} catch (Exception e) {
|
||||
String volumeToString = getReflectOnlySelectedFields(vol);
|
||||
s_logger.debug(String.format("Unable to check and repair volume [%s] on host [%s], due to %s.", volumeToString, host, e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitionException {
|
||||
return _volStateMachine.transitTo(vol, event, null, _volsDao);
|
||||
}
|
||||
|
||||
@ -32,8 +32,10 @@ import java.util.concurrent.ExecutionException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import com.cloud.storage.VolumeApiServiceImpl;
|
||||
import org.apache.cloudstack.annotation.AnnotationService;
|
||||
import org.apache.cloudstack.annotation.dao.AnnotationDao;
|
||||
import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd;
|
||||
import org.apache.cloudstack.engine.cloud.entity.api.VolumeEntity;
|
||||
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
|
||||
@ -80,6 +82,7 @@ import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO;
|
||||
import org.apache.cloudstack.storage.image.store.TemplateObject;
|
||||
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@ -88,9 +91,12 @@ import org.springframework.stereotype.Component;
|
||||
import com.cloud.agent.AgentManager;
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.ModifyTargetsCommand;
|
||||
import com.cloud.agent.api.storage.CheckAndRepairVolumeAnswer;
|
||||
import com.cloud.agent.api.storage.CheckAndRepairVolumeCommand;
|
||||
import com.cloud.agent.api.storage.ListVolumeAnswer;
|
||||
import com.cloud.agent.api.storage.ListVolumeCommand;
|
||||
import com.cloud.agent.api.storage.ResizeVolumeCommand;
|
||||
import com.cloud.agent.api.to.DataObjectType;
|
||||
import com.cloud.agent.api.to.StorageFilerTO;
|
||||
import com.cloud.agent.api.to.VirtualMachineTO;
|
||||
import com.cloud.alert.AlertManager;
|
||||
@ -111,6 +117,7 @@ import com.cloud.org.Cluster;
|
||||
import com.cloud.org.Grouping.AllocationState;
|
||||
import com.cloud.resource.ResourceState;
|
||||
import com.cloud.server.ManagementService;
|
||||
import com.cloud.storage.CheckAndRepairVolumePayload;
|
||||
import com.cloud.storage.DataStoreRole;
|
||||
import com.cloud.storage.RegisterVolumePayload;
|
||||
import com.cloud.storage.ScopeType;
|
||||
@ -200,7 +207,7 @@ public class VolumeServiceImpl implements VolumeService {
|
||||
@Inject
|
||||
private VolumeOrchestrationService _volumeMgr;
|
||||
@Inject
|
||||
private StorageManager _storageMgr;
|
||||
protected StorageManager _storageMgr;
|
||||
@Inject
|
||||
private AnnotationDao annotationDao;
|
||||
@Inject
|
||||
@ -2776,6 +2783,62 @@ public class VolumeServiceImpl implements VolumeService {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkAndRepairVolumeBasedOnConfig(DataObject dataObject, Host host) {
|
||||
if (HypervisorType.KVM.equals(host.getHypervisorType()) && DataObjectType.VOLUME.equals(dataObject.getType())) {
|
||||
VolumeInfo volumeInfo = volFactory.getVolume(dataObject.getId());
|
||||
if (VolumeApiServiceImpl.AllowCheckAndRepairVolume.valueIn(volumeInfo.getPoolId())) {
|
||||
s_logger.info(String.format("Trying to check and repair the volume %d", dataObject.getId()));
|
||||
String repair = CheckAndRepairVolumeCmd.RepairValues.LEAKS.name().toLowerCase();
|
||||
CheckAndRepairVolumePayload payload = new CheckAndRepairVolumePayload(repair);
|
||||
volumeInfo.addPayload(payload);
|
||||
checkAndRepairVolumeThroughHost(volumeInfo, host);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, String> checkAndRepairVolume(VolumeInfo volume) {
|
||||
Long poolId = volume.getPoolId();
|
||||
List<Long> hostIds = _storageMgr.getUpHostsInPool(poolId);
|
||||
if (CollectionUtils.isEmpty(hostIds)) {
|
||||
throw new CloudRuntimeException("Unable to find Up hosts to run the check volume command");
|
||||
}
|
||||
Collections.shuffle(hostIds);
|
||||
Host host = _hostDao.findById(hostIds.get(0));
|
||||
|
||||
return checkAndRepairVolumeThroughHost(volume, host);
|
||||
|
||||
}
|
||||
|
||||
private Pair<String, String> checkAndRepairVolumeThroughHost(VolumeInfo volume, Host host) {
|
||||
Long poolId = volume.getPoolId();
|
||||
StoragePool pool = _storageMgr.getStoragePool(poolId);
|
||||
CheckAndRepairVolumePayload payload = (CheckAndRepairVolumePayload) volume.getpayload();
|
||||
CheckAndRepairVolumeCommand command = new CheckAndRepairVolumeCommand(volume.getPath(), new StorageFilerTO(pool), payload.getRepair(),
|
||||
volume.getPassphrase(), volume.getEncryptFormat());
|
||||
|
||||
try {
|
||||
grantAccess(volume, host, volume.getDataStore());
|
||||
CheckAndRepairVolumeAnswer answer = (CheckAndRepairVolumeAnswer) _storageMgr.sendToPool(pool, new long[]{host.getId()}, command);
|
||||
if (answer != null && answer.getResult()) {
|
||||
s_logger.debug(String.format("Check volume response result: %s", answer.getDetails()));
|
||||
return new Pair<>(answer.getVolumeCheckExecutionResult(), answer.getVolumeRepairExecutionResult());
|
||||
} else {
|
||||
String errMsg = (answer == null) ? null : answer.getDetails();
|
||||
s_logger.debug(String.format("Failed to check and repair the volume with error %s", errMsg));
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
s_logger.debug("sending check and repair volume command failed", e);
|
||||
} finally {
|
||||
revokeAccess(volume, host, volume.getDataStore());
|
||||
command.clearPassphrase();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// For managed storage on Xen and VMware, we need to potentially make space for hypervisor snapshots.
|
||||
// The disk offering can collect this information and pass it on to the volume that's about to be created.
|
||||
// Ex. if you want a 10 GB CloudStack volume to reside on managed storage on Xen, this leads to an SR
|
||||
|
||||
@ -19,15 +19,12 @@
|
||||
|
||||
package org.apache.cloudstack.storage.volume;
|
||||
|
||||
import com.cloud.storage.Storage;
|
||||
import com.cloud.storage.VolumeVO;
|
||||
import com.cloud.storage.dao.VolumeDao;
|
||||
import com.cloud.storage.snapshot.SnapshotManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
|
||||
@ -42,6 +39,23 @@ import org.mockito.Mockito;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import com.cloud.agent.api.storage.CheckAndRepairVolumeAnswer;
|
||||
import com.cloud.agent.api.storage.CheckAndRepairVolumeCommand;
|
||||
import com.cloud.agent.api.to.StorageFilerTO;
|
||||
import com.cloud.exception.StorageUnavailableException;
|
||||
import com.cloud.host.HostVO;
|
||||
import com.cloud.host.dao.HostDao;
|
||||
import com.cloud.storage.CheckAndRepairVolumePayload;
|
||||
import com.cloud.storage.Storage;
|
||||
import com.cloud.storage.StorageManager;
|
||||
import com.cloud.storage.StoragePool;
|
||||
import com.cloud.storage.VolumeVO;
|
||||
import com.cloud.storage.dao.VolumeDao;
|
||||
import com.cloud.storage.snapshot.SnapshotManager;
|
||||
import com.cloud.utils.Pair;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class VolumeServiceTest extends TestCase{
|
||||
|
||||
@ -66,15 +80,26 @@ public class VolumeServiceTest extends TestCase{
|
||||
@Mock
|
||||
SnapshotManager snapshotManagerMock;
|
||||
|
||||
@Mock
|
||||
StorageManager storageManagerMock;
|
||||
|
||||
@Mock
|
||||
VolumeVO volumeVoMock;
|
||||
|
||||
@Mock
|
||||
HostVO hostMock;
|
||||
|
||||
@Mock
|
||||
HostDao hostDaoMock;
|
||||
|
||||
@Before
|
||||
public void setup(){
|
||||
volumeServiceImplSpy = Mockito.spy(new VolumeServiceImpl());
|
||||
volumeServiceImplSpy.volFactory = volumeDataFactoryMock;
|
||||
volumeServiceImplSpy.volDao = volumeDaoMock;
|
||||
volumeServiceImplSpy.snapshotMgr = snapshotManagerMock;
|
||||
volumeServiceImplSpy._storageMgr = storageManagerMock;
|
||||
volumeServiceImplSpy._hostDao = hostDaoMock;
|
||||
}
|
||||
|
||||
@Test(expected = InterruptedException.class)
|
||||
@ -213,4 +238,75 @@ public class VolumeServiceTest extends TestCase{
|
||||
volumeServiceImplSpy.destroySourceVolumeAfterMigration(ObjectInDataStoreStateMachine.Event.DestroyRequested, null, volumeObject,
|
||||
volumeObject, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckAndRepairVolume() throws StorageUnavailableException {
|
||||
VolumeInfo volume = Mockito.mock(VolumeInfo.class);
|
||||
Mockito.when(volume.getPoolId()).thenReturn(1L);
|
||||
StoragePool pool = Mockito.mock(StoragePool.class);
|
||||
Mockito.when(storageManagerMock.getStoragePool(1L)).thenReturn(pool);
|
||||
List<Long> hostIds = new ArrayList<>();
|
||||
hostIds.add(1L);
|
||||
Mockito.when(storageManagerMock.getUpHostsInPool(1L)).thenReturn(hostIds);
|
||||
Mockito.when(hostMock.getId()).thenReturn(1L);
|
||||
Mockito.when(hostDaoMock.findById(1L)).thenReturn(hostMock);
|
||||
|
||||
CheckAndRepairVolumePayload payload = new CheckAndRepairVolumePayload(null);
|
||||
Mockito.when(volume.getpayload()).thenReturn(payload);
|
||||
Mockito.when(volume.getPath()).thenReturn("cbac516a-0f1f-4559-921c-1a7c6c408ccf");
|
||||
Mockito.when(volume.getPassphrase()).thenReturn(new byte[] {3, 1, 2, 3});
|
||||
Mockito.when(volume.getEncryptFormat()).thenReturn("LUKS");
|
||||
|
||||
String checkResult = "{\n" +
|
||||
" \"image-end-offset\": 6442582016,\n" +
|
||||
" \"total-clusters\": 163840,\n" +
|
||||
" \"check-errors\": 0,\n" +
|
||||
" \"leaks\": 124,\n" +
|
||||
" \"allocated-clusters\": 98154,\n" +
|
||||
" \"filename\": \"/var/lib/libvirt/images/26be20c7-b9d0-43f6-a76e-16c70737a0e0\",\n" +
|
||||
" \"format\": \"qcow2\",\n" +
|
||||
" \"fragmented-clusters\": 96135\n" +
|
||||
"}";
|
||||
|
||||
CheckAndRepairVolumeCommand command = new CheckAndRepairVolumeCommand(volume.getPath(), new StorageFilerTO(pool), payload.getRepair(),
|
||||
volume.getPassphrase(), volume.getEncryptFormat());
|
||||
|
||||
CheckAndRepairVolumeAnswer answer = new CheckAndRepairVolumeAnswer(command, true, checkResult);
|
||||
answer.setVolumeCheckExecutionResult(checkResult);
|
||||
Mockito.when(storageManagerMock.sendToPool(pool, new long[]{1L}, command)).thenReturn(answer);
|
||||
|
||||
Pair<String, String> result = volumeServiceImplSpy.checkAndRepairVolume(volume);
|
||||
|
||||
Assert.assertEquals(result.first(), checkResult);
|
||||
Assert.assertEquals(result.second(), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckAndRepairVolumeWhenFailure() throws StorageUnavailableException {
|
||||
VolumeInfo volume = Mockito.mock(VolumeInfo.class);
|
||||
Mockito.when(volume.getPoolId()).thenReturn(1L);
|
||||
StoragePool pool = Mockito.mock(StoragePool.class);
|
||||
Mockito.when(storageManagerMock.getStoragePool(1L)).thenReturn(pool);
|
||||
List<Long> hostIds = new ArrayList<>();
|
||||
hostIds.add(1L);
|
||||
Mockito.when(storageManagerMock.getUpHostsInPool(1L)).thenReturn(hostIds);
|
||||
Mockito.when(hostMock.getId()).thenReturn(1L);
|
||||
Mockito.when(hostDaoMock.findById(1L)).thenReturn(hostMock);
|
||||
|
||||
CheckAndRepairVolumePayload payload = new CheckAndRepairVolumePayload(null);
|
||||
Mockito.when(volume.getpayload()).thenReturn(payload);
|
||||
Mockito.when(volume.getPath()).thenReturn("cbac516a-0f1f-4559-921c-1a7c6c408ccf");
|
||||
Mockito.when(volume.getPassphrase()).thenReturn(new byte[] {3, 1, 2, 3});
|
||||
Mockito.when(volume.getEncryptFormat()).thenReturn("LUKS");
|
||||
|
||||
CheckAndRepairVolumeCommand command = new CheckAndRepairVolumeCommand(volume.getPath(), new StorageFilerTO(pool), payload.getRepair(),
|
||||
volume.getPassphrase(), volume.getEncryptFormat());
|
||||
|
||||
CheckAndRepairVolumeAnswer answer = new CheckAndRepairVolumeAnswer(command, false, "Unable to execute qemu command");
|
||||
Mockito.when(storageManagerMock.sendToPool(pool, new long[]{1L}, command)).thenReturn(answer);
|
||||
|
||||
Pair<String, String> result = volumeServiceImplSpy.checkAndRepairVolume(volume);
|
||||
|
||||
Assert.assertEquals(null, result);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,192 @@
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you 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 com.cloud.hypervisor.kvm.resource.wrapper;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.storage.CheckAndRepairVolumeCommand;
|
||||
import com.cloud.agent.api.storage.CheckAndRepairVolumeAnswer;
|
||||
import com.cloud.agent.api.to.StorageFilerTO;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
|
||||
import com.cloud.resource.CommandWrapper;
|
||||
import com.cloud.resource.ResourceWrapper;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.apache.cloudstack.utils.cryptsetup.KeyFile;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImageOptions;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImgException;
|
||||
import org.apache.cloudstack.utils.qemu.QemuImgFile;
|
||||
import org.apache.cloudstack.utils.qemu.QemuObject;
|
||||
import org.apache.cloudstack.utils.qemu.QemuObject.EncryptFormat;
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.libvirt.LibvirtException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@ResourceWrapper(handles = CheckAndRepairVolumeCommand.class)
|
||||
public class LibvirtCheckAndRepairVolumeCommandWrapper extends CommandWrapper<CheckAndRepairVolumeCommand, Answer, LibvirtComputingResource> {
|
||||
|
||||
private static final Logger s_logger = Logger.getLogger(LibvirtCheckAndRepairVolumeCommandWrapper.class);
|
||||
|
||||
@Override
|
||||
public Answer execute(CheckAndRepairVolumeCommand command, LibvirtComputingResource serverResource) {
|
||||
final String volumeId = command.getPath();
|
||||
final String repair = command.getRepair();
|
||||
final StorageFilerTO spool = command.getPool();
|
||||
|
||||
final KVMStoragePoolManager storagePoolMgr = serverResource.getStoragePoolMgr();
|
||||
KVMStoragePool pool = storagePoolMgr.getStoragePool(spool.getType(), spool.getUuid());
|
||||
final KVMPhysicalDisk vol = pool.getPhysicalDisk(volumeId);
|
||||
byte[] passphrase = command.getPassphrase();
|
||||
|
||||
try {
|
||||
CheckAndRepairVolumeAnswer answer = null;
|
||||
String checkVolumeResult = null;
|
||||
if (QemuImg.PhysicalDiskFormat.RAW.equals(vol.getFormat())) {
|
||||
checkVolumeResult = "Volume format RAW is not supported to check and repair";
|
||||
String jsonStringFormat = String.format("{ \"message\": \"%s\" }", checkVolumeResult);
|
||||
answer = new CheckAndRepairVolumeAnswer(command, true, checkVolumeResult);
|
||||
answer.setVolumeCheckExecutionResult(jsonStringFormat);
|
||||
|
||||
return answer;
|
||||
} else {
|
||||
answer = checkVolume(vol, command, serverResource);
|
||||
checkVolumeResult = answer.getVolumeCheckExecutionResult();
|
||||
}
|
||||
|
||||
CheckAndRepairVolumeAnswer resultAnswer = checkIfRepairLeaksIsRequired(command, checkVolumeResult, vol.getName());
|
||||
// resultAnswer is not null when repair is not required, so return from here
|
||||
if (resultAnswer != null) {
|
||||
return resultAnswer;
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(repair)) {
|
||||
answer = repairVolume(vol, command, serverResource, checkVolumeResult);
|
||||
}
|
||||
|
||||
return answer;
|
||||
} catch (Exception e) {
|
||||
return new CheckAndRepairVolumeAnswer(command, false, e.toString());
|
||||
} finally {
|
||||
if (passphrase != null) {
|
||||
Arrays.fill(passphrase, (byte) 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CheckAndRepairVolumeAnswer checkVolume(KVMPhysicalDisk vol, CheckAndRepairVolumeCommand command, LibvirtComputingResource serverResource) {
|
||||
EncryptFormat encryptFormat = EncryptFormat.enumValue(command.getEncryptFormat());
|
||||
byte[] passphrase = command.getPassphrase();
|
||||
String checkVolumeResult = checkAndRepairVolume(vol, null, encryptFormat, passphrase, serverResource);
|
||||
s_logger.info(String.format("Check Volume result for the volume %s is %s", vol.getName(), checkVolumeResult));
|
||||
CheckAndRepairVolumeAnswer answer = new CheckAndRepairVolumeAnswer(command, true, checkVolumeResult);
|
||||
answer.setVolumeCheckExecutionResult(checkVolumeResult);
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
private CheckAndRepairVolumeAnswer repairVolume(KVMPhysicalDisk vol, CheckAndRepairVolumeCommand command, LibvirtComputingResource serverResource, String checkVolumeResult) {
|
||||
EncryptFormat encryptFormat = EncryptFormat.enumValue(command.getEncryptFormat());
|
||||
byte[] passphrase = command.getPassphrase();
|
||||
final String repair = command.getRepair();
|
||||
|
||||
String repairVolumeResult = checkAndRepairVolume(vol, repair, encryptFormat, passphrase, serverResource);
|
||||
String finalResult = (checkVolumeResult != null ? checkVolumeResult.concat(",") : "") + repairVolumeResult;
|
||||
s_logger.info(String.format("Repair Volume result for the volume %s is %s", vol.getName(), repairVolumeResult));
|
||||
|
||||
CheckAndRepairVolumeAnswer answer = new CheckAndRepairVolumeAnswer(command, true, finalResult);
|
||||
answer.setVolumeRepairExecutionResult(repairVolumeResult);
|
||||
answer.setVolumeCheckExecutionResult(checkVolumeResult);
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
private CheckAndRepairVolumeAnswer checkIfRepairLeaksIsRequired(CheckAndRepairVolumeCommand command, String checkVolumeResult, String volumeName) {
|
||||
final String repair = command.getRepair();
|
||||
int leaks = 0;
|
||||
if (StringUtils.isNotEmpty(checkVolumeResult) && StringUtils.isNotEmpty(repair) && repair.equals("leaks")) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
JsonNode jsonNode = null;
|
||||
try {
|
||||
jsonNode = objectMapper.readTree(checkVolumeResult);
|
||||
} catch (JsonProcessingException e) {
|
||||
String msg = String.format("Error processing response %s during check volume %s", checkVolumeResult, e.getMessage());
|
||||
s_logger.info(msg);
|
||||
|
||||
return skipRepairVolumeCommand(command, checkVolumeResult, msg);
|
||||
}
|
||||
JsonNode leaksNode = jsonNode.get("leaks");
|
||||
if (leaksNode != null) {
|
||||
leaks = leaksNode.asInt();
|
||||
}
|
||||
|
||||
if (leaks == 0) {
|
||||
String msg = String.format("No leaks found while checking for the volume %s, so skipping repair", volumeName);
|
||||
return skipRepairVolumeCommand(command, checkVolumeResult, msg);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private CheckAndRepairVolumeAnswer skipRepairVolumeCommand(CheckAndRepairVolumeCommand command, String checkVolumeResult, String msg) {
|
||||
s_logger.info(msg);
|
||||
String jsonStringFormat = String.format("{ \"message\": \"%s\" }", msg);
|
||||
String finalResult = (checkVolumeResult != null ? checkVolumeResult.concat(",") : "") + jsonStringFormat;
|
||||
CheckAndRepairVolumeAnswer answer = new CheckAndRepairVolumeAnswer(command, true, finalResult);
|
||||
answer.setVolumeRepairExecutionResult(jsonStringFormat);
|
||||
answer.setVolumeCheckExecutionResult(checkVolumeResult);
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
protected String checkAndRepairVolume(final KVMPhysicalDisk vol, final String repair, final EncryptFormat encryptFormat, byte[] passphrase, final LibvirtComputingResource libvirtComputingResource) throws CloudRuntimeException {
|
||||
List<QemuObject> passphraseObjects = new ArrayList<>();
|
||||
QemuImageOptions imgOptions = null;
|
||||
if (ArrayUtils.isEmpty(passphrase)) {
|
||||
passphrase = null;
|
||||
}
|
||||
try (KeyFile keyFile = new KeyFile(passphrase)) {
|
||||
if (passphrase != null) {
|
||||
passphraseObjects.add(
|
||||
QemuObject.prepareSecretForQemuImg(vol.getFormat(), encryptFormat, keyFile.toString(), "sec0", null)
|
||||
);
|
||||
imgOptions = new QemuImageOptions(vol.getFormat(), vol.getPath(),"sec0");
|
||||
}
|
||||
QemuImg q = new QemuImg(libvirtComputingResource.getCmdsTimeout());
|
||||
QemuImgFile file = new QemuImgFile(vol.getPath());
|
||||
return q.checkAndRepair(file, imgOptions, passphraseObjects, repair);
|
||||
} catch (QemuImgException | LibvirtException ex) {
|
||||
throw new CloudRuntimeException("Failed to run qemu-img for check volume", ex);
|
||||
} catch (IOException ex) {
|
||||
throw new CloudRuntimeException("Failed to create keyfile for encrypted volume for check volume operation", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -813,4 +813,45 @@ public class QemuImg {
|
||||
Pattern pattern = Pattern.compile("Supported\\sformats:[a-zA-Z0-9-_\\s]*?\\b" + format + "\\b", CASE_INSENSITIVE);
|
||||
return pattern.matcher(text).find();
|
||||
}
|
||||
|
||||
/**
|
||||
* check for any leaks for an image and repair.
|
||||
*
|
||||
* @param imageOptions
|
||||
* Qemu style image options to be used in the checking process.
|
||||
* @param qemuObjects
|
||||
* Qemu style options (e.g. for passing secrets).
|
||||
* @param repair
|
||||
* Boolean option whether to repair any leaks
|
||||
*/
|
||||
public String checkAndRepair(final QemuImgFile file, final QemuImageOptions imageOptions, final List<QemuObject> qemuObjects, final String repair) throws QemuImgException {
|
||||
final Script script = new Script(_qemuImgPath);
|
||||
script.add("check");
|
||||
if (imageOptions == null) {
|
||||
script.add(file.getFileName());
|
||||
}
|
||||
|
||||
for (QemuObject o : qemuObjects) {
|
||||
script.add(o.toCommandFlag());
|
||||
}
|
||||
|
||||
if (imageOptions != null) {
|
||||
script.add(imageOptions.toCommandFlag());
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(repair)) {
|
||||
script.add("-r");
|
||||
script.add(repair);
|
||||
}
|
||||
|
||||
script.add("--output=json");
|
||||
script.add("2>/dev/null");
|
||||
|
||||
final String result = Script.runBashScriptIgnoreExitValue(script.toString(), 3);
|
||||
if (result != null) {
|
||||
logger.debug(String.format("Check volume execution result %s", result));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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 com.cloud.hypervisor.kvm.resource.wrapper;
|
||||
|
||||
import com.cloud.agent.api.storage.CheckAndRepairVolumeAnswer;
|
||||
import com.cloud.agent.api.storage.CheckAndRepairVolumeCommand;
|
||||
import com.cloud.agent.api.to.StorageFilerTO;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
|
||||
import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
|
||||
import com.cloud.storage.Storage;
|
||||
|
||||
import org.apache.cloudstack.utils.qemu.QemuImg;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedConstruction;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class LibvirtCheckAndRepairVolumeCommandWrapperTest {
|
||||
|
||||
@Spy
|
||||
LibvirtCheckAndRepairVolumeCommandWrapper libvirtCheckAndRepairVolumeCommandWrapperSpy = Mockito.spy(LibvirtCheckAndRepairVolumeCommandWrapper.class);
|
||||
|
||||
@Mock
|
||||
LibvirtComputingResource libvirtComputingResourceMock;
|
||||
|
||||
@Mock
|
||||
CheckAndRepairVolumeCommand checkAndRepairVolumeCommand;
|
||||
|
||||
@Mock
|
||||
QemuImg qemuImgMock;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
when(libvirtComputingResourceMock.getCmdsTimeout()).thenReturn(60);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckAndRepairVolume() throws Exception {
|
||||
|
||||
CheckAndRepairVolumeCommand cmd = Mockito.mock(CheckAndRepairVolumeCommand.class);
|
||||
when(cmd.getPath()).thenReturn("cbac516a-0f1f-4559-921c-1a7c6c408ccf");
|
||||
when(cmd.getRepair()).thenReturn(null);
|
||||
StorageFilerTO spool = Mockito.mock(StorageFilerTO.class);
|
||||
when(cmd.getPool()).thenReturn(spool);
|
||||
|
||||
KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class);
|
||||
when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolMgr);
|
||||
KVMStoragePool pool = Mockito.mock(KVMStoragePool.class);
|
||||
when(spool.getType()).thenReturn(Storage.StoragePoolType.PowerFlex);
|
||||
when(spool.getUuid()).thenReturn("b6be258b-42b8-49a4-ad51-3634ef8ff76a");
|
||||
when(storagePoolMgr.getStoragePool(Storage.StoragePoolType.PowerFlex, "b6be258b-42b8-49a4-ad51-3634ef8ff76a")).thenReturn(pool);
|
||||
|
||||
KVMPhysicalDisk vol = Mockito.mock(KVMPhysicalDisk.class);
|
||||
when(pool.getPhysicalDisk("cbac516a-0f1f-4559-921c-1a7c6c408ccf")).thenReturn(vol);
|
||||
Mockito.when(vol.getFormat()).thenReturn(QemuImg.PhysicalDiskFormat.QCOW2);
|
||||
|
||||
String checkResult = "{\n" +
|
||||
" \"image-end-offset\": 6442582016,\n" +
|
||||
" \"total-clusters\": 163840,\n" +
|
||||
" \"check-errors\": 0,\n" +
|
||||
" \"leaks\": 124,\n" +
|
||||
" \"allocated-clusters\": 98154,\n" +
|
||||
" \"filename\": \"/var/lib/libvirt/images/26be20c7-b9d0-43f6-a76e-16c70737a0e0\",\n" +
|
||||
" \"format\": \"qcow2\",\n" +
|
||||
" \"fragmented-clusters\": 96135\n" +
|
||||
"}";
|
||||
|
||||
try (MockedConstruction<QemuImg> ignored = Mockito.mockConstruction(QemuImg.class, (mock, context) -> {
|
||||
when(mock.checkAndRepair(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(checkResult);
|
||||
})) {
|
||||
CheckAndRepairVolumeAnswer result = (CheckAndRepairVolumeAnswer) libvirtCheckAndRepairVolumeCommandWrapperSpy.execute(cmd, libvirtComputingResourceMock);
|
||||
Assert.assertEquals(checkResult, result.getVolumeCheckExecutionResult());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -87,6 +87,9 @@ public class LibvirtStoragePoolTest extends TestCase {
|
||||
StoragePool storage = Mockito.mock(StoragePool.class);
|
||||
|
||||
LibvirtStoragePool nfsPool = new LibvirtStoragePool(uuid, name, StoragePoolType.NetworkFilesystem, adapter, storage);
|
||||
if (nfsPool.getType() != StoragePoolType.NetworkFilesystem) {
|
||||
System.out.println("tested");
|
||||
}
|
||||
assertFalse(nfsPool.isExternalSnapshot());
|
||||
|
||||
LibvirtStoragePool rbdPool = new LibvirtStoragePool(uuid, name, StoragePoolType.RBD, adapter, storage);
|
||||
|
||||
@ -368,4 +368,21 @@ public class QemuImgTest {
|
||||
Assert.assertTrue("should support qcow2", QemuImg.helpSupportsImageFormat(partialHelp, PhysicalDiskFormat.QCOW2));
|
||||
Assert.assertFalse("should not support http", QemuImg.helpSupportsImageFormat(partialHelp, PhysicalDiskFormat.SHEEPDOG));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckAndRepair() throws LibvirtException {
|
||||
String filename = "/tmp/" + UUID.randomUUID() + ".qcow2";
|
||||
|
||||
QemuImgFile file = new QemuImgFile(filename);
|
||||
|
||||
try {
|
||||
QemuImg qemu = new QemuImg(0);
|
||||
qemu.checkAndRepair(file, null, null, null);
|
||||
} catch (QemuImgException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
File f = new File(filename);
|
||||
f.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@ -4769,7 +4769,13 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
|
||||
final String vmName = cmd.getVmName();
|
||||
try {
|
||||
VmwareHypervisorHost hyperHost = getHyperHost(getServiceContext());
|
||||
if (hyperHost == null) {
|
||||
throw new CloudRuntimeException("no hypervisor host found for migrate command");
|
||||
}
|
||||
ManagedObjectReference morDc = hyperHost.getHyperHostDatacenter();
|
||||
if (morDc == null) {
|
||||
throw new CloudRuntimeException("no Managed Object Reference for the Data Center found for migrate command");
|
||||
}
|
||||
|
||||
// find VM through datacenter (VM is not at the target host yet)
|
||||
VirtualMachineMO vmMo = hyperHost.findVmOnPeerHyperHost(vmName);
|
||||
@ -4780,6 +4786,9 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
|
||||
}
|
||||
|
||||
VmwareHypervisorHost destHyperHost = getTargetHyperHost(new DatacenterMO(hyperHost.getContext(), morDc), cmd.getDestinationIp());
|
||||
if (destHyperHost == null) {
|
||||
throw new CloudRuntimeException("no destination Hypervisor Host found for migrate command");
|
||||
}
|
||||
|
||||
ManagedObjectReference morTargetPhysicalHost = destHyperHost.findMigrationTarget(vmMo);
|
||||
if (morTargetPhysicalHost == null) {
|
||||
@ -4791,7 +4800,8 @@ public class VmwareResource extends ServerResourceBase implements StoragePoolRes
|
||||
}
|
||||
|
||||
return new MigrateAnswer(cmd, true, "migration succeeded", null);
|
||||
} catch (Throwable e) {
|
||||
} catch (Exception e) {
|
||||
s_logger.info(String.format("migrate command for %s failed due to %s", vmName, e.getLocalizedMessage()));
|
||||
return new MigrateAnswer(cmd, false, createLogMessageException(e, cmd), null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -551,6 +551,7 @@ import org.apache.cloudstack.api.command.user.volume.AddResourceDetailCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.ChangeOfferingForVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.DeleteVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.DestroyVolumeCmd;
|
||||
@ -3790,6 +3791,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
|
||||
cmdList.add(ListVMGroupsCmd.class);
|
||||
cmdList.add(UpdateVMGroupCmd.class);
|
||||
cmdList.add(AttachVolumeCmd.class);
|
||||
cmdList.add(CheckAndRepairVolumeCmd.class);
|
||||
cmdList.add(CreateVolumeCmd.class);
|
||||
cmdList.add(DeleteVolumeCmd.class);
|
||||
cmdList.add(UpdateVolumeCmd.class);
|
||||
|
||||
@ -1710,17 +1710,21 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc
|
||||
storagePoolStats.put(pool.getId(), (StorageStats)answer);
|
||||
|
||||
boolean poolNeedsUpdating = false;
|
||||
long capacityBytes = ((StorageStats)answer).getCapacityBytes();
|
||||
long usedBytes = ((StorageStats)answer).getByteUsed();
|
||||
// Seems like we have dynamically updated the pool size since the prev. size and the current do not match
|
||||
if (_storagePoolStats.get(poolId) != null && _storagePoolStats.get(poolId).getCapacityBytes() != ((StorageStats)answer).getCapacityBytes()) {
|
||||
if (((StorageStats)answer).getCapacityBytes() > 0) {
|
||||
pool.setCapacityBytes(((StorageStats)answer).getCapacityBytes());
|
||||
if ((_storagePoolStats.get(poolId) != null && _storagePoolStats.get(poolId).getCapacityBytes() != capacityBytes)
|
||||
|| pool.getCapacityBytes() != capacityBytes) {
|
||||
if (capacityBytes > 0) {
|
||||
pool.setCapacityBytes(capacityBytes);
|
||||
poolNeedsUpdating = true;
|
||||
} else {
|
||||
logger.warn("Not setting capacity bytes, received " + ((StorageStats)answer).getCapacityBytes() + " capacity for pool ID " + poolId);
|
||||
}
|
||||
}
|
||||
if (pool.getUsedBytes() != ((StorageStats)answer).getByteUsed() && (pool.getStorageProviderName().equalsIgnoreCase(DataStoreProvider.DEFAULT_PRIMARY) || _storageManager.canPoolProvideStorageStats(pool))) {
|
||||
pool.setUsedBytes(((StorageStats) answer).getByteUsed());
|
||||
if (((_storagePoolStats.get(poolId) != null && _storagePoolStats.get(poolId).getByteUsed() != usedBytes)
|
||||
|| pool.getUsedBytes() != usedBytes) && (pool.getStorageProviderName().equalsIgnoreCase(DataStoreProvider.DEFAULT_PRIMARY) || _storageManager.canPoolProvideStorageStats(pool))) {
|
||||
pool.setUsedBytes(usedBytes);
|
||||
poolNeedsUpdating = true;
|
||||
}
|
||||
if (poolNeedsUpdating) {
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you 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 com.cloud.storage;
|
||||
|
||||
public class CheckAndRepairVolumePayload {
|
||||
|
||||
public final String repair;
|
||||
public String result;
|
||||
|
||||
public CheckAndRepairVolumePayload(String repair) {
|
||||
this.repair = repair;
|
||||
}
|
||||
|
||||
public String getRepair() {
|
||||
return repair;
|
||||
}
|
||||
|
||||
public String getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(String result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
}
|
||||
@ -42,6 +42,7 @@ import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.command.user.volume.AssignVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.AttachVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.ChangeOfferingForVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.ExtractVolumeCmd;
|
||||
@ -216,6 +217,7 @@ import com.cloud.vm.VirtualMachineManager;
|
||||
import com.cloud.vm.VmDetailConstants;
|
||||
import com.cloud.vm.VmWork;
|
||||
import com.cloud.vm.VmWorkAttachVolume;
|
||||
import com.cloud.vm.VmWorkCheckAndRepairVolume;
|
||||
import com.cloud.vm.VmWorkConstants;
|
||||
import com.cloud.vm.VmWorkDetachVolume;
|
||||
import com.cloud.vm.VmWorkExtractVolume;
|
||||
@ -377,6 +379,9 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
public static ConfigKey<Long> storageTagRuleExecutionTimeout = new ConfigKey<>("Advanced", Long.class, "storage.tag.rule.execution.timeout", "2000", "The maximum runtime,"
|
||||
+ " in milliseconds, to execute a storage tag rule; if it is reached, a timeout will happen.", true);
|
||||
|
||||
public static final ConfigKey<Boolean> AllowCheckAndRepairVolume = new ConfigKey<Boolean>("Advanced", Boolean.class, "volume.check.and.repair.leaks.before.use", "false",
|
||||
"To check and repair the volume if it has any leaks before performing volume attach or VM start operations", true, ConfigKey.Scope.StoragePool);
|
||||
|
||||
private final StateMachine2<Volume.State, Volume.Event, Volume> _volStateMachine;
|
||||
|
||||
private static final Set<Volume.State> STATES_VOLUME_CANNOT_BE_DESTROYED = new HashSet<>(Arrays.asList(Volume.State.Destroy, Volume.State.Expunging, Volume.State.Expunged, Volume.State.Allocated));
|
||||
@ -1333,7 +1338,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
outcome.get();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Operation was interrupted", e);
|
||||
} catch (java.util.concurrent.ExecutionException e) {
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException("Execution exception", e);
|
||||
}
|
||||
|
||||
@ -1816,7 +1821,158 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
logger.debug(String.format("Volume [%s] has been successfully recovered, thus a new usage event %s has been published.", volume.getUuid(), EventTypes.EVENT_VOLUME_CREATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_CHECK, eventDescription = "checking volume and repair if needed", async = true)
|
||||
public Pair<String, String> checkAndRepairVolume(CheckAndRepairVolumeCmd cmd) throws ResourceAllocationException {
|
||||
long volumeId = cmd.getId();
|
||||
String repair = cmd.getRepair();
|
||||
|
||||
final VolumeVO volume = _volsDao.findById(volumeId);
|
||||
validationsForCheckVolumeOperation(volume);
|
||||
|
||||
Long vmId = volume.getInstanceId();
|
||||
if (vmId != null) {
|
||||
// serialize VM operation
|
||||
return handleCheckAndRepairVolumeJob(vmId, volumeId, repair);
|
||||
} else {
|
||||
return handleCheckAndRepairVolume(volumeId, repair);
|
||||
}
|
||||
}
|
||||
|
||||
private Pair<String, String> handleCheckAndRepairVolume(Long volumeId, String repair) {
|
||||
CheckAndRepairVolumePayload payload = new CheckAndRepairVolumePayload(repair);
|
||||
VolumeInfo volumeInfo = volFactory.getVolume(volumeId);
|
||||
volumeInfo.addPayload(payload);
|
||||
|
||||
Pair<String, String> result = volService.checkAndRepairVolume(volumeInfo);
|
||||
return result;
|
||||
}
|
||||
|
||||
private Pair<String, String> handleCheckAndRepairVolumeJob(Long vmId, Long volumeId, String repair) throws ResourceAllocationException {
|
||||
AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext();
|
||||
if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) {
|
||||
// avoid re-entrance
|
||||
VmWorkJobVO placeHolder = null;
|
||||
placeHolder = createPlaceHolderWork(vmId);
|
||||
try {
|
||||
Pair<String, String> result = orchestrateCheckAndRepairVolume(volumeId, repair);
|
||||
return result;
|
||||
} finally {
|
||||
_workJobDao.expunge(placeHolder.getId());
|
||||
}
|
||||
} else {
|
||||
Outcome<Pair> outcome = checkAndRepairVolumeThroughJobQueue(vmId, volumeId, repair);
|
||||
try {
|
||||
outcome.get();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Operation is interrupted", e);
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException("Execution exception--", e);
|
||||
}
|
||||
|
||||
Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob());
|
||||
if (jobResult != null) {
|
||||
if (jobResult instanceof ConcurrentOperationException) {
|
||||
throw (ConcurrentOperationException)jobResult;
|
||||
} else if (jobResult instanceof ResourceAllocationException) {
|
||||
throw (ResourceAllocationException)jobResult;
|
||||
} else if (jobResult instanceof Throwable) {
|
||||
throw new RuntimeException("Unexpected exception", (Throwable)jobResult);
|
||||
}
|
||||
}
|
||||
|
||||
// retrieve the entity url from job result
|
||||
if (jobResult != null && jobResult instanceof Pair) {
|
||||
return (Pair<String, String>) jobResult;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void validationsForCheckVolumeOperation(VolumeVO volume) {
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
_accountMgr.checkAccess(caller, null, true, volume);
|
||||
|
||||
String volumeName = volume.getName();
|
||||
Long vmId = volume.getInstanceId();
|
||||
if (vmId != null) {
|
||||
validateVMforCheckVolumeOperation(vmId, volumeName);
|
||||
}
|
||||
|
||||
if (volume.getState() != Volume.State.Ready) {
|
||||
throw new InvalidParameterValueException(String.format("Volume: %s is not in Ready state", volumeName));
|
||||
}
|
||||
|
||||
HypervisorType hypervisorType = _volsDao.getHypervisorType(volume.getId());
|
||||
if (!HypervisorType.KVM.equals(hypervisorType)) {
|
||||
throw new InvalidParameterValueException(String.format("Check and Repair volumes is supported only for KVM hypervisor"));
|
||||
}
|
||||
|
||||
if (!Arrays.asList(ImageFormat.QCOW2, ImageFormat.VDI).contains(volume.getFormat())) {
|
||||
throw new InvalidParameterValueException("Volume format is not supported for checking and repair");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateVMforCheckVolumeOperation(Long vmId, String volumeName) {
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
UserVmVO vm = _userVmDao.findById(vmId);
|
||||
if (vm == null) {
|
||||
throw new InvalidParameterValueException(String.format("VM not found, please check the VM to which this volume %s is attached", volumeName));
|
||||
}
|
||||
|
||||
_accountMgr.checkAccess(caller, null, true, vm);
|
||||
|
||||
if (vm.getState() != State.Stopped) {
|
||||
throw new InvalidParameterValueException(String.format("VM to which the volume %s is attached should be in stopped state", volumeName));
|
||||
}
|
||||
}
|
||||
|
||||
private Pair<String, String> orchestrateCheckAndRepairVolume(Long volumeId, String repair) {
|
||||
|
||||
VolumeInfo volume = volFactory.getVolume(volumeId);
|
||||
|
||||
if (volume == null) {
|
||||
throw new InvalidParameterValueException("Checking volume and repairing failed due to volume:" + volumeId + " doesn't exist");
|
||||
}
|
||||
|
||||
CheckAndRepairVolumePayload payload = new CheckAndRepairVolumePayload(repair);
|
||||
volume.addPayload(payload);
|
||||
|
||||
return volService.checkAndRepairVolume(volume);
|
||||
}
|
||||
|
||||
public Outcome<Pair> checkAndRepairVolumeThroughJobQueue(final Long vmId, final Long volumeId, String repair) {
|
||||
|
||||
final CallContext context = CallContext.current();
|
||||
final User callingUser = context.getCallingUser();
|
||||
final Account callingAccount = context.getCallingAccount();
|
||||
|
||||
final VMInstanceVO vm = _vmInstanceDao.findById(vmId);
|
||||
|
||||
VmWorkJobVO workJob = new VmWorkJobVO(context.getContextId());
|
||||
|
||||
workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_DISPATCHER);
|
||||
workJob.setCmd(VmWorkCheckAndRepairVolume.class.getName());
|
||||
|
||||
workJob.setAccountId(callingAccount.getId());
|
||||
workJob.setUserId(callingUser.getId());
|
||||
workJob.setStep(VmWorkJobVO.Step.Starting);
|
||||
workJob.setVmType(VirtualMachine.Type.Instance);
|
||||
workJob.setVmInstanceId(vm.getId());
|
||||
workJob.setRelated(AsyncJobExecutionContext.getOriginJobId());
|
||||
|
||||
// save work context info (there are some duplications)
|
||||
VmWorkCheckAndRepairVolume workInfo = new VmWorkCheckAndRepairVolume(callingUser.getId(), callingAccount.getId(), vm.getId(),
|
||||
VolumeApiServiceImpl.VM_WORK_JOB_HANDLER, volumeId, repair);
|
||||
workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo));
|
||||
|
||||
_jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId());
|
||||
|
||||
AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId());
|
||||
|
||||
return new VmJobCheckAndRepairVolumeOutcome(workJob);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_VOLUME_CHANGE_DISK_OFFERING, eventDescription = "Changing disk offering of a volume")
|
||||
@ -1986,7 +2142,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
outcome.get();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Operation was interrupted", e);
|
||||
} catch (java.util.concurrent.ExecutionException e) {
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException("Execution exception", e);
|
||||
}
|
||||
|
||||
@ -2773,7 +2929,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
outcome.get();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Operation is interrupted", e);
|
||||
} catch (java.util.concurrent.ExecutionException e) {
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException("Execution excetion", e);
|
||||
}
|
||||
|
||||
@ -3181,7 +3337,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
outcome.get();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Operation is interrupted", e);
|
||||
} catch (java.util.concurrent.ExecutionException e) {
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException("Execution excetion", e);
|
||||
}
|
||||
|
||||
@ -3510,7 +3666,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
outcome.get();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Operation is interrupted", e);
|
||||
} catch (java.util.concurrent.ExecutionException e) {
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException("Execution excetion", e);
|
||||
}
|
||||
|
||||
@ -3827,7 +3983,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
outcome.get();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Operation is interrupted", e);
|
||||
} catch (java.util.concurrent.ExecutionException e) {
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException("Execution excetion", e);
|
||||
}
|
||||
|
||||
@ -4249,6 +4405,12 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
try {
|
||||
// if we don't have a host, the VM we are attaching the disk to has never been started before
|
||||
if (host != null) {
|
||||
try {
|
||||
volService.checkAndRepairVolumeBasedOnConfig(volFactory.getVolume(volumeToAttach.getId()), host);
|
||||
} catch (Exception e) {
|
||||
s_logger.debug(String.format("Unable to check and repair volume [%s] on host [%s], due to %s.", volumeToAttach.getName(), host, e.getMessage()));
|
||||
}
|
||||
|
||||
try {
|
||||
volService.grantAccess(volFactory.getVolume(volumeToAttach.getId()), host, dataStore);
|
||||
} catch (Exception e) {
|
||||
@ -4594,6 +4756,24 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
}
|
||||
}
|
||||
|
||||
public class VmJobCheckAndRepairVolumeOutcome extends OutcomeImpl<Pair> {
|
||||
|
||||
public VmJobCheckAndRepairVolumeOutcome(final AsyncJob job) {
|
||||
super(Pair.class, job, VmJobCheckInterval.value(), new Predicate() {
|
||||
@Override
|
||||
public boolean checkCondition() {
|
||||
AsyncJobVO jobVo = _entityMgr.findById(AsyncJobVO.class, job.getId());
|
||||
assert (jobVo != null);
|
||||
if (jobVo == null || jobVo.getStatus() != JobInfo.Status.IN_PROGRESS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}, AsyncJob.Topics.JOB_STATE);
|
||||
}
|
||||
}
|
||||
|
||||
public Outcome<Volume> attachVolumeToVmThroughJobQueue(final Long vmId, final Long volumeId, final Long deviceId) {
|
||||
|
||||
final CallContext context = CallContext.current();
|
||||
@ -4831,6 +5011,13 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
return new Pair<JobInfo.Status, String>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(work.getSnapshotId()));
|
||||
}
|
||||
|
||||
@ReflectionUse
|
||||
private Pair<JobInfo.Status, String> orchestrateCheckAndRepairVolume(VmWorkCheckAndRepairVolume work) throws Exception {
|
||||
Account account = _accountDao.findById(work.getAccountId());
|
||||
Pair<String, String> result = orchestrateCheckAndRepairVolume(work.getVolumeId(), work.getRepair());
|
||||
return new Pair<JobInfo.Status, String>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<JobInfo.Status, String> handleVmWorkJob(VmWork work) throws Exception {
|
||||
return _jobHandlerProxy.handleVmWorkJob(work);
|
||||
@ -4867,7 +5054,8 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic
|
||||
AllowUserExpungeRecoverVolume,
|
||||
MatchStoragePoolTagsWithDiskOffering,
|
||||
UseHttpsToUpload,
|
||||
WaitDetachDevice
|
||||
WaitDetachDevice,
|
||||
AllowCheckAndRepairVolume
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@ -39,6 +40,7 @@ import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.apache.cloudstack.acl.ControlledEntity;
|
||||
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
|
||||
import org.apache.cloudstack.api.command.user.volume.CheckAndRepairVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.CreateVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.DetachVolumeCmd;
|
||||
import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd;
|
||||
@ -1642,7 +1644,6 @@ public class VolumeApiServiceImplTest {
|
||||
Mockito.when(_diskOfferingDao.findById(1L)).thenReturn(diskOffering);
|
||||
|
||||
StoragePoolVO srcStoragePoolVOMock = Mockito.mock(StoragePoolVO.class);
|
||||
StoragePool destPool = Mockito.mock(StoragePool.class);
|
||||
PrimaryDataStore dataStore = Mockito.mock(PrimaryDataStore.class);
|
||||
|
||||
Mockito.when(vol.getPassphraseId()).thenReturn(1L);
|
||||
@ -1657,4 +1658,166 @@ public class VolumeApiServiceImplTest {
|
||||
// test passed
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidationsForCheckVolumeAPI() {
|
||||
VolumeVO volume = mock(VolumeVO.class);
|
||||
|
||||
AccountVO account = new AccountVO("admin", 1L, "networkDomain", Account.Type.NORMAL, "uuid");
|
||||
UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
|
||||
CallContext.register(user, account);
|
||||
|
||||
lenient().doNothing().when(accountManagerMock).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class));
|
||||
|
||||
when(volume.getInstanceId()).thenReturn(1L);
|
||||
UserVmVO vm = mock(UserVmVO.class);
|
||||
when(userVmDaoMock.findById(1L)).thenReturn(vm);
|
||||
when(vm.getState()).thenReturn(State.Stopped);
|
||||
when(volume.getState()).thenReturn(Volume.State.Ready);
|
||||
when(volume.getId()).thenReturn(1L);
|
||||
when(volumeDaoMock.getHypervisorType(1L)).thenReturn(HypervisorType.KVM);
|
||||
when(volume.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
|
||||
|
||||
volumeApiServiceImpl.validationsForCheckVolumeOperation(volume);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testValidationsForCheckVolumeAPIWithRunningVM() {
|
||||
VolumeVO volume = mock(VolumeVO.class);
|
||||
|
||||
AccountVO account = new AccountVO("admin", 1L, "networkDomain", Account.Type.NORMAL, "uuid");
|
||||
UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
|
||||
CallContext.register(user, account);
|
||||
|
||||
lenient().doNothing().when(accountManagerMock).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class));
|
||||
|
||||
when(volume.getInstanceId()).thenReturn(1L);
|
||||
UserVmVO vm = mock(UserVmVO.class);
|
||||
when(userVmDaoMock.findById(1L)).thenReturn(vm);
|
||||
when(vm.getState()).thenReturn(State.Running);
|
||||
|
||||
volumeApiServiceImpl.validationsForCheckVolumeOperation(volume);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testValidationsForCheckVolumeAPIWithNonexistedVM() {
|
||||
VolumeVO volume = mock(VolumeVO.class);
|
||||
|
||||
AccountVO account = new AccountVO("admin", 1L, "networkDomain", Account.Type.NORMAL, "uuid");
|
||||
UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
|
||||
CallContext.register(user, account);
|
||||
|
||||
lenient().doNothing().when(accountManagerMock).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class));
|
||||
|
||||
when(volume.getInstanceId()).thenReturn(1L);
|
||||
when(userVmDaoMock.findById(1L)).thenReturn(null);
|
||||
|
||||
volumeApiServiceImpl.validationsForCheckVolumeOperation(volume);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testValidationsForCheckVolumeAPIWithAllocatedVolume() {
|
||||
VolumeVO volume = mock(VolumeVO.class);
|
||||
|
||||
AccountVO account = new AccountVO("admin", 1L, "networkDomain", Account.Type.NORMAL, "uuid");
|
||||
UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
|
||||
CallContext.register(user, account);
|
||||
|
||||
lenient().doNothing().when(accountManagerMock).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class));
|
||||
|
||||
when(volume.getInstanceId()).thenReturn(1L);
|
||||
UserVmVO vm = mock(UserVmVO.class);
|
||||
when(userVmDaoMock.findById(1L)).thenReturn(vm);
|
||||
when(vm.getState()).thenReturn(State.Stopped);
|
||||
when(volume.getState()).thenReturn(Volume.State.Allocated);
|
||||
|
||||
volumeApiServiceImpl.validationsForCheckVolumeOperation(volume);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testValidationsForCheckVolumeAPIWithNonKVMhypervisor() {
|
||||
VolumeVO volume = mock(VolumeVO.class);
|
||||
|
||||
AccountVO account = new AccountVO("admin", 1L, "networkDomain", Account.Type.NORMAL, "uuid");
|
||||
UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
|
||||
CallContext.register(user, account);
|
||||
|
||||
lenient().doNothing().when(accountManagerMock).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class));
|
||||
|
||||
when(volume.getInstanceId()).thenReturn(1L);
|
||||
UserVmVO vm = mock(UserVmVO.class);
|
||||
when(userVmDaoMock.findById(1L)).thenReturn(vm);
|
||||
when(vm.getState()).thenReturn(State.Stopped);
|
||||
when(volume.getState()).thenReturn(Volume.State.Ready);
|
||||
when(volume.getId()).thenReturn(1L);
|
||||
when(volumeDaoMock.getHypervisorType(1L)).thenReturn(HypervisorType.VMware);
|
||||
|
||||
volumeApiServiceImpl.validationsForCheckVolumeOperation(volume);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckAndRepairVolume() throws ResourceAllocationException {
|
||||
|
||||
CheckAndRepairVolumeCmd cmd = mock(CheckAndRepairVolumeCmd.class);
|
||||
when(cmd.getId()).thenReturn(1L);
|
||||
when(cmd.getRepair()).thenReturn(null);
|
||||
|
||||
VolumeVO volume = mock(VolumeVO.class);
|
||||
when(volumeDaoMock.findById(1L)).thenReturn(volume);
|
||||
|
||||
AccountVO account = new AccountVO("admin", 1L, "networkDomain", Account.Type.NORMAL, "uuid");
|
||||
UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
|
||||
CallContext.register(user, account);
|
||||
|
||||
lenient().doNothing().when(accountManagerMock).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class));
|
||||
|
||||
when(volume.getInstanceId()).thenReturn(null);
|
||||
when(volume.getState()).thenReturn(Volume.State.Ready);
|
||||
when(volume.getId()).thenReturn(1L);
|
||||
when(volumeDaoMock.getHypervisorType(1L)).thenReturn(HypervisorType.KVM);
|
||||
|
||||
VolumeInfo volumeInfo = mock(VolumeInfo.class);
|
||||
when(volumeDataFactoryMock.getVolume(1L)).thenReturn(volumeInfo);
|
||||
|
||||
String checkResult = "{\n" +
|
||||
" \"image-end-offset\": 6442582016,\n" +
|
||||
" \"total-clusters\": 163840,\n" +
|
||||
" \"check-errors\": 0,\n" +
|
||||
" \"leaks\": 124,\n" +
|
||||
" \"allocated-clusters\": 98154,\n" +
|
||||
" \"filename\": \"/var/lib/libvirt/images/26be20c7-b9d0-43f6-a76e-16c70737a0e0\",\n" +
|
||||
" \"format\": \"qcow2\",\n" +
|
||||
" \"fragmented-clusters\": 96135\n" +
|
||||
"}";
|
||||
|
||||
String repairResult = null;
|
||||
Pair<String, String> result = new Pair<>(checkResult, repairResult);
|
||||
when(volumeServiceMock.checkAndRepairVolume(volumeInfo)).thenReturn(result);
|
||||
when(volume.getFormat()).thenReturn(Storage.ImageFormat.QCOW2);
|
||||
|
||||
Pair<String, String> finalresult = volumeApiServiceImpl.checkAndRepairVolume(cmd);
|
||||
|
||||
Assert.assertEquals(result, finalresult);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testValidationsForCheckVolumeAPIWithInvalidVolumeFormat() {
|
||||
VolumeVO volume = mock(VolumeVO.class);
|
||||
AccountVO account = new AccountVO("admin", 1L, "networkDomain", Account.Type.NORMAL, "uuid");
|
||||
UserVO user = new UserVO(1, "testuser", "password", "firstname", "lastName", "email", "timezone", UUID.randomUUID().toString(), User.Source.UNKNOWN);
|
||||
CallContext.register(user, account);
|
||||
|
||||
lenient().doNothing().when(accountManagerMock).checkAccess(any(Account.class), any(AccessType.class), any(Boolean.class), any(ControlledEntity.class));
|
||||
|
||||
when(volume.getInstanceId()).thenReturn(1L);
|
||||
UserVmVO vm = mock(UserVmVO.class);
|
||||
when(userVmDaoMock.findById(1L)).thenReturn(vm);
|
||||
when(vm.getState()).thenReturn(State.Stopped);
|
||||
when(volume.getState()).thenReturn(Volume.State.Ready);
|
||||
when(volume.getId()).thenReturn(1L);
|
||||
when(volumeDaoMock.getHypervisorType(1L)).thenReturn(HypervisorType.KVM);
|
||||
when(volume.getFormat()).thenReturn(Storage.ImageFormat.RAW);
|
||||
|
||||
volumeApiServiceImpl.validationsForCheckVolumeOperation(volume);
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,13 +16,6 @@
|
||||
// under the License.
|
||||
package com.cloud.consoleproxy;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketException;
|
||||
import org.eclipse.jetty.websocket.api.extensions.Frame;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
@ -30,6 +23,13 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketException;
|
||||
import org.eclipse.jetty.websocket.api.extensions.Frame;
|
||||
|
||||
import com.cloud.consoleproxy.vnc.NoVncClient;
|
||||
|
||||
public class ConsoleProxyNoVncClient implements ConsoleProxyClient {
|
||||
@ -115,11 +115,6 @@ public class ConsoleProxyNoVncClient implements ConsoleProxyClient {
|
||||
updateFrontEndActivityTime();
|
||||
}
|
||||
connectionAlive = session.isOpen();
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("Error on sleep for vnc over websocket", e);
|
||||
}
|
||||
} else if (client.isVncOverNioSocket()) {
|
||||
byte[] bytesArr;
|
||||
int nextBytes = client.getNextBytes();
|
||||
@ -140,6 +135,11 @@ public class ConsoleProxyNoVncClient implements ConsoleProxyClient {
|
||||
connectionAlive = false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("Error on sleep for vnc sessions", e);
|
||||
}
|
||||
}
|
||||
logger.info(String.format("Connection with client [%s] is dead.", clientId));
|
||||
} catch (IOException e) {
|
||||
|
||||
@ -3062,7 +3062,7 @@
|
||||
"message.remove.vpc": "Please confirm that you want to remove the VPC",
|
||||
"message.request.failed": "Request failed.",
|
||||
"message.required.add.least.ip": "Please add at least 1 IP Range",
|
||||
"message.required.traffic.type": "All required traffic types should be added and with multiple physical networks each network should have a label.",
|
||||
"message.required.traffic.type": "All required traffic types should be added and with multiple physical networks each traffic type should have a label.",
|
||||
"message.required.tagged.physical.network": "There can only be one untagged physical network with guest traffic type.",
|
||||
"message.reset.vpn.connection": "Please confirm that you want to reset VPN connection.",
|
||||
"message.resize.volume.failed": "Failed to resize volume.",
|
||||
|
||||
@ -42,6 +42,11 @@ export default {
|
||||
name: 'guestnetwork',
|
||||
title: 'label.guest.networks',
|
||||
param: 'physicalnetworkid'
|
||||
},
|
||||
{
|
||||
name: 'publicip',
|
||||
title: 'label.public.ip.addresses',
|
||||
param: 'physicalnetworkid'
|
||||
}],
|
||||
actions: [
|
||||
{
|
||||
|
||||
@ -337,7 +337,7 @@ export default {
|
||||
name: 'vnfapp',
|
||||
title: 'label.vnf.appliances',
|
||||
icon: 'gateway-outlined',
|
||||
permission: ['listVirtualMachinesMetrics'],
|
||||
permission: ['listVnfTemplates'],
|
||||
resourceType: 'UserVm',
|
||||
params: () => {
|
||||
return { details: 'servoff,tmpl,nics', isvnf: true }
|
||||
|
||||
@ -54,6 +54,12 @@
|
||||
<div
|
||||
class="actions"
|
||||
style="text-align: right" >
|
||||
<router-link :to="{ name: 'publicip', query: { vlanid: record.id }}" target="_blank">
|
||||
<tooltip-button
|
||||
tooltipPlacement="bottom"
|
||||
:tooltip="$t('label.view') + ' ' + $t('label.public.ip.addresses')"
|
||||
icon="environment-outlined"/>
|
||||
</router-link>
|
||||
<tooltip-button
|
||||
v-if="!record.domain && !basicGuestNetwork && record.gateway && !record.ip6gateway"
|
||||
tooltipPlacement="bottom"
|
||||
|
||||
@ -19,6 +19,10 @@
|
||||
|
||||
package com.cloud.utils;
|
||||
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@ -282,4 +286,22 @@ public class StringUtils {
|
||||
final String value = keyValuePair.substring(index + 1);
|
||||
return new Pair<>(key.trim(), value.trim());
|
||||
}
|
||||
|
||||
public static Map<String, String> parseJsonToMap(String jsonString) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
Map<String, String> mapResult = new HashMap<>();
|
||||
|
||||
if (org.apache.commons.lang3.StringUtils.isNotBlank(jsonString)) {
|
||||
try {
|
||||
JsonNode jsonNode = objectMapper.readTree(jsonString);
|
||||
jsonNode.fields().forEachRemaining(entry -> {
|
||||
mapResult.put(entry.getKey(), entry.getValue().asText());
|
||||
});
|
||||
} catch (Exception e) {
|
||||
throw new CloudRuntimeException("Error while parsing json to convert it to map " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return mapResult;
|
||||
}
|
||||
}
|
||||
|
||||
@ -331,6 +331,118 @@ public class Script implements Callable<String> {
|
||||
}
|
||||
}
|
||||
|
||||
public String executeIgnoreExitValue(OutputInterpreter interpreter, int exitValue) {
|
||||
String[] command = _command.toArray(new String[_command.size()]);
|
||||
|
||||
if (_logger.isDebugEnabled()) {
|
||||
_logger.debug(String.format("Executing: %s", buildCommandLine(command).split(KeyStoreUtils.KS_FILENAME)[0]));
|
||||
}
|
||||
|
||||
try {
|
||||
ProcessBuilder pb = new ProcessBuilder(command);
|
||||
pb.redirectErrorStream(true);
|
||||
if (_workDir != null)
|
||||
pb.directory(new File(_workDir));
|
||||
|
||||
_process = pb.start();
|
||||
if (_process == null) {
|
||||
_logger.warn(String.format("Unable to execute: %s", buildCommandLine(command)));
|
||||
return String.format("Unable to execute the command: %s", command[0]);
|
||||
}
|
||||
|
||||
BufferedReader ir = new BufferedReader(new InputStreamReader(_process.getInputStream()));
|
||||
|
||||
_thread = Thread.currentThread();
|
||||
ScheduledFuture<String> future = null;
|
||||
if (_timeout > 0) {
|
||||
future = s_executors.schedule(this, _timeout, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
Task task = null;
|
||||
if (interpreter != null && interpreter.drain()) {
|
||||
task = new Task(interpreter, ir);
|
||||
s_executors.execute(task);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
_logger.debug(String.format("Executing while with timeout : %d", _timeout));
|
||||
try {
|
||||
//process execution completed within timeout period
|
||||
if (_process.waitFor(_timeout, TimeUnit.MILLISECONDS)) {
|
||||
//process completed successfully
|
||||
if (_process.exitValue() == 0 || _process.exitValue() == exitValue) {
|
||||
_logger.debug("Execution is successful.");
|
||||
if (interpreter != null) {
|
||||
return interpreter.drain() ? task.getResult() : interpreter.interpret(ir);
|
||||
} else {
|
||||
// null return exitValue apparently
|
||||
return String.valueOf(_process.exitValue());
|
||||
}
|
||||
} else { //process failed
|
||||
break;
|
||||
}
|
||||
} //timeout
|
||||
} catch (InterruptedException e) {
|
||||
if (!_isTimeOut) {
|
||||
/*
|
||||
* This is not timeout, we are interrupted by others,
|
||||
* continue
|
||||
*/
|
||||
_logger.debug("We are interrupted but it's not a timeout, just continue");
|
||||
continue;
|
||||
}
|
||||
} finally {
|
||||
if (future != null) {
|
||||
future.cancel(false);
|
||||
}
|
||||
Thread.interrupted();
|
||||
}
|
||||
|
||||
//timeout without completing the process
|
||||
TimedOutLogger log = new TimedOutLogger(_process);
|
||||
Task timedoutTask = new Task(log, ir);
|
||||
|
||||
timedoutTask.run();
|
||||
if (!_passwordCommand) {
|
||||
_logger.warn(String.format("Timed out: %s. Output is: %s", buildCommandLine(command), timedoutTask.getResult()));
|
||||
} else {
|
||||
_logger.warn(String.format("Timed out: %s", buildCommandLine(command)));
|
||||
}
|
||||
|
||||
return ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
_logger.debug(String.format("Exit value is %d", _process.exitValue()));
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(_process.getInputStream()), 128);
|
||||
|
||||
String error;
|
||||
if (interpreter != null) {
|
||||
error = interpreter.processError(reader);
|
||||
} else {
|
||||
error = String.valueOf(_process.exitValue());
|
||||
}
|
||||
|
||||
if (_logger.isDebugEnabled()) {
|
||||
_logger.debug(error);
|
||||
}
|
||||
return error;
|
||||
} catch (SecurityException ex) {
|
||||
_logger.warn("Security Exception....not running as root?", ex);
|
||||
return stackTraceAsString(ex);
|
||||
} catch (Exception ex) {
|
||||
_logger.warn(String.format("Exception: %s", buildCommandLine(command)), ex);
|
||||
return stackTraceAsString(ex);
|
||||
} finally {
|
||||
if (_process != null) {
|
||||
IOUtils.closeQuietly(_process.getErrorStream());
|
||||
IOUtils.closeQuietly(_process.getOutputStream());
|
||||
IOUtils.closeQuietly(_process.getInputStream());
|
||||
_process.destroyForcibly();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String call() {
|
||||
try {
|
||||
@ -564,4 +676,24 @@ public class Script implements Callable<String> {
|
||||
}
|
||||
}
|
||||
|
||||
public static String runBashScriptIgnoreExitValue(String command, int exitValue) {
|
||||
return runBashScriptIgnoreExitValue(command, exitValue, 0);
|
||||
}
|
||||
|
||||
public static String runBashScriptIgnoreExitValue(String command, int exitValue, int timeout) {
|
||||
|
||||
Script s = new Script("/bin/bash", timeout);
|
||||
s.add("-c");
|
||||
s.add(command);
|
||||
|
||||
OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
|
||||
s.executeIgnoreExitValue(parser, exitValue);
|
||||
|
||||
String result = parser.getLines();
|
||||
if (result == null || result.trim().isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return result.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user