Skip to content
Open
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.cloud.agent.resource.kvm.wrapper;

import com.cloud.agent.resource.kvm.LibvirtComputingResource;
import com.cloud.common.request.ResourceWrapper;
import com.cloud.legacymodel.communication.answer.Answer;
import com.cloud.legacymodel.communication.answer.MigrationProgressAnswer;
import com.cloud.legacymodel.communication.command.MigrationProgressCommand;

import org.libvirt.Connect;
import org.libvirt.Domain;
import org.libvirt.DomainJobInfo;
import org.libvirt.LibvirtException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ResourceWrapper(handles = MigrationProgressCommand.class)
public final class LibvirtGetDomJobInfoWrapper extends LibvirtCommandWrapper<MigrationProgressCommand, Answer, LibvirtComputingResource> {

private static final Logger s_logger = LoggerFactory.getLogger(LibvirtGetDomJobInfoWrapper.class);

@Override
public Answer execute(final MigrationProgressCommand command, final LibvirtComputingResource libvirtComputingResource) {
final String vmName = command.getVmName();
final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper();
final Connect conn;
DomainJobInfo domainJobInfo;
Domain vm;

try {
conn = libvirtUtilitiesHelper.getConnectionByVmName(vmName);
vm = libvirtComputingResource.getDomain(conn, vmName);
domainJobInfo = vm.getJobInfo();
} catch (LibvirtException e) {
final String msg = " Getting domain job info failed due to " + e.toString();
s_logger.warn(msg, e);
return new MigrationProgressAnswer(command, false, msg);
}

return new MigrationProgressAnswer(command, true, null,
domainJobInfo.getTimeElapsed(), domainJobInfo.getTimeRemaining(),
domainJobInfo.getDataTotal(), domainJobInfo.getDataProcessed(), domainJobInfo.getDataRemaining(),
domainJobInfo.getMemTotal(), domainJobInfo.getMemProcessed(), domainJobInfo.getMemRemaining(),
domainJobInfo.getFileTotal(), domainJobInfo.getFileProcessed(), domainJobInfo.getFileRemaining()
);
}
}
1 change: 1 addition & 0 deletions cosmic-client/src/main/resources/commands.properties
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ migrateVirtualMachineWithVolume=1
recoverVirtualMachine=15
expungeVirtualMachine=15
getVirtualMachineUserData=15
getVmProgress=15
#### snapshot commands
createSnapshot=15
listSnapshots=31
Expand Down
50 changes: 50 additions & 0 deletions cosmic-client/src/main/webapp/css/cloudstack3.css
Original file line number Diff line number Diff line change
Expand Up @@ -12429,3 +12429,53 @@ div.gpugroups div.list-view {
background: transparent url("../images/icons.png") no-repeat -626px -209px;
padding: 0 0 3px 18px;
}

.ui-progressbar {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border: none;
border-radius: 5px;
width: 25%;
height: 15px;
z-index: 0;
position: relative;
background-color: #eee;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25) inset;
display: inline-table;
}

.ui-progressbar-value {
position: absolute;
width: 0;
height: inherit;
background-image: -webkit-linear-gradient(-45deg, transparent 33%, rgba(0, 0, 0, .1) 33%, rgba(0, 0, 0, .1) 66%, transparent 66%), -webkit-linear-gradient(top, rgba(255, 255, 255, .66), rgba(37, 79, 163, .66));
border-radius: inherit;
background-size: 35px 20px, 100% 100%, 100% 100%;
text-align: center;
-webkit-animation: animate-stripes 5s linear infinite;
animation: animate-stripes 5s linear infinite;
line-height: 15px;
}

.ui-progressbar-value span {
font-size: larger;
}

.progress-grid {
display: flex !important;
align-content: center;
grid-gap: 10px;
}

@-webkit-keyframes animate-stripes {
100% {
background-position: 100px 0px;
}
}

@keyframes animate-stripes {
100% {
background-position: 100px 0px;
}
}⏎
8 changes: 5 additions & 3 deletions cosmic-client/src/main/webapp/scripts/instances.js
Original file line number Diff line number Diff line change
Expand Up @@ -2098,19 +2098,21 @@
},
state: {
label: 'label.state',
pollMigrationProgress: true,
pollAgainIfValueIsIn: {
'Starting': 1,
'Stopping': 1
'Stopping': 1,
'Migrating': 1
},
pollAgainFn: function (context) {
var toClearInterval = false;
$.ajax({
url: createURL("listVirtualMachines&id=" + context.instances[0].id),
url: createURL("listVirtualMachines&id=" + context.id),
dataType: "json",
async: false,
success: function (json) {
var jsonObj = json.listvirtualmachinesresponse.virtualmachine[0];
if (jsonObj.state != context.instances[0].state) {
if (jsonObj.state !== context.state) {
toClearInterval = true; //to clear interval
}
}
Expand Down
34 changes: 34 additions & 0 deletions cosmic-client/src/main/webapp/scripts/ui/widgets/detailView.js
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,40 @@
$value.data('detail-view-is-password', value.isPassword);
}

if (typeof(value.pollAgainFn) === "function") {
for(let state in value.pollAgainIfValueIsIn) {
let interval_id = 0;
if (content === state) {
if (!value.pollAgainFn(data)) {
if (value.pollMigrationProgress) {
interval_id = setInterval(function () {
$.ajax({
url: createURL("getVmProgress&uuid=" + context.instances[0].id),
dataType: "json",
async: false
}).done(function (jsonObj) {
let json = jsonObj.getvmprogressresponse.getvmprogressresponse;
if (json.timeremaining === 0) {
clearInterval(interval_id);
} else {
let percentage = (json.dataprocessed / (json.datatotal & 1) * 100);
if (percentage >= 100) {
percentage = 100;
}
$("#migration-progress").progressbar({value: percentage})
.children('.ui-progressbar-value')
.html('<span>' + percentage + '%</span>');
}
}).fail(function (json) {
clearInterval(interval_id);
});
}, 1000);
}
}
}
}
}

return true;
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.cloud.api.command.user.vm;

import com.cloud.api.APICommand;
import com.cloud.api.APICommandGroup;
import com.cloud.api.ApiConstants;
import com.cloud.api.ApiErrorCode;
import com.cloud.api.BaseCmd;
import com.cloud.api.Parameter;
import com.cloud.api.ServerApiException;
import com.cloud.api.response.VmProgressResponse;
import com.cloud.legacymodel.exceptions.CloudRuntimeException;
import com.cloud.legacymodel.exceptions.InvalidParameterValueException;
import com.cloud.legacymodel.exceptions.ResourceUnavailableException;
import com.cloud.legacymodel.user.Account;
import com.cloud.uservm.UserVm;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@APICommand(name = "getVmProgress", group = APICommandGroup.VirtualMachineService, description = "Get migration progress of VM", responseObject = VmProgressResponse.class)
public class GetVMProgressCmd extends BaseCmd {
public static final Logger s_logger = LoggerFactory.getLogger(GetVMProgressCmd.class.getName());

private static final String COMMAND_NAME = "getvmprogressresponse";
@Parameter(name = ApiConstants.UUID, type = BaseCmd.CommandType.STRING, required = true, description = "The UUID of the VM.")
private String uuid;

@Override
public void execute() {

try {
final VmProgressResponse response = _userVmService.getVmProgress(this);
response.setResponseName(getCommandName());
response.setObjectName(getCommandName());
setResponseObject(response);
} catch (InvalidParameterValueException e) {
s_logger.error("Invalid parameter: " + e.getMessage());
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid parameter");
} catch (CloudRuntimeException e) {
s_logger.error("CloudRuntimeException: " + e.getMessage());
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to get progress");
} catch (Exception e) {
s_logger.error("Unexpected exception: " + e.getMessage());
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Unexpected exception");
}
}

@Override
public String getCommandName() {
return COMMAND_NAME;
}

@Override
public long getEntityOwnerId() {
final UserVm userVm = _entityMgr.findByUuid(UserVm.class, getUuid());
if (userVm != null) {
return userVm.getAccountId();
}

return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked
}

public String getUuid() {
return uuid;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package com.cloud.api.response;

import com.cloud.api.ApiConstants;
import com.cloud.api.BaseResponse;
import com.cloud.serializer.Param;

import com.google.gson.annotations.SerializedName;

public class VmProgressResponse extends BaseResponse {

@SerializedName("timeelapsed")
@Param(description = "Time elapsed")
long timeElapsed;

@SerializedName("timeremaining")
@Param(description = "Time remaining")
long timeRemaining;

@SerializedName("datatotal")
@Param(description = "Data total")
long dataTotal;

@SerializedName("dataprocessed")
@Param(description = "Data processed")
long dataProcessed;

@SerializedName("dataremaining")
@Param(description = "Data remaining")
long dataRemaining;

@SerializedName("memorytotal")
@Param(description = "Memory total")
long memTotal;

@SerializedName("memoryprocessed")
@Param(description = "Memory processed")
long memProcessed;

@SerializedName("memoryremaining")
@Param(description = "Memory remaining")
long memRemaining;

@SerializedName("filetotal")
@Param(description = "File total")
long fileTotal;

@SerializedName("fileprocessed")
@Param(description = "File processed")
long fileProcessed;

@SerializedName("fileremaining")
@Param(description = "File remaining")
long fileRemaining;

public long getTimeElapsed() {
return timeElapsed;
}

public void setTimeElapsed(final long timeElapsed) {
this.timeElapsed = timeElapsed;
}

public long getTimeRemaining() {
return timeRemaining;
}

public void setTimeRemaining(final long timeRemaining) {
this.timeRemaining = timeRemaining;
}

public long getDataTotal() {
return dataTotal;
}

public void setDataTotal(final long dataTotal) {
this.dataTotal = dataTotal;
}

public long getDataProcessed() {
return dataProcessed;
}

public void setDataProcessed(final long dataProcessed) {
this.dataProcessed = dataProcessed;
}

public long getDataRemaining() {
return dataRemaining;
}

public void setDataRemaining(final long dataRemaining) {
this.dataRemaining = dataRemaining;
}

public long getMemTotal() {
return memTotal;
}

public void setMemTotal(final long memTotal) {
this.memTotal = memTotal;
}

public long getMemProcessed() {
return memProcessed;
}

public void setMemProcessed(final long memProcessed) {
this.memProcessed = memProcessed;
}

public long getMemRemaining() {
return memRemaining;
}

public void setMemRemaining(final long memRemaining) {
this.memRemaining = memRemaining;
}

public long getFileTotal() {
return fileTotal;
}

public void setFileTotal(final long fileTotal) {
this.fileTotal = fileTotal;
}

public long getFileProcessed() {
return fileProcessed;
}

public void setFileProcessed(final long fileProcessed) {
this.fileProcessed = fileProcessed;
}

public long getFileRemaining() {
return fileRemaining;
}

public void setFileRemaining(final long fileRemaining) {
this.fileRemaining = fileRemaining;
}
}
Loading