Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Migration progress #931

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
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