mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Add live migration of system VMs (KVM) (#6491)
Co-authored-by: Rodrigo D. Lopez <19981369+RodrigoDLopez@users.noreply.github.com>
This commit is contained in:
parent
f580a8d7a2
commit
23033fbb74
@ -434,6 +434,8 @@ public interface UserVmService {
|
|||||||
|
|
||||||
UserVm getUserVm(long vmId);
|
UserVm getUserVm(long vmId);
|
||||||
|
|
||||||
|
VirtualMachine getVm(long vmId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Migrate the given VM to the destination host provided. The API returns the migrated VM if migration succeeds.
|
* Migrate the given VM to the destination host provided. The API returns the migrated VM if migration succeeds.
|
||||||
* Only Root
|
* Only Root
|
||||||
|
|||||||
@ -455,6 +455,7 @@ public class ApiConstants {
|
|||||||
public static final String VM_AVAILABLE = "vmavailable";
|
public static final String VM_AVAILABLE = "vmavailable";
|
||||||
public static final String VM_LIMIT = "vmlimit";
|
public static final String VM_LIMIT = "vmlimit";
|
||||||
public static final String VM_TOTAL = "vmtotal";
|
public static final String VM_TOTAL = "vmtotal";
|
||||||
|
public static final String VM_TYPE = "vmtype";
|
||||||
public static final String VNET = "vnet";
|
public static final String VNET = "vnet";
|
||||||
public static final String IS_VOLATILE = "isvolatile";
|
public static final String IS_VOLATILE = "isvolatile";
|
||||||
public static final String VOLUME_ID = "volumeid";
|
public static final String VOLUME_ID = "volumeid";
|
||||||
@ -619,6 +620,7 @@ public class ApiConstants {
|
|||||||
public static final String TRAFFIC_TYPE_IMPLEMENTOR = "traffictypeimplementor";
|
public static final String TRAFFIC_TYPE_IMPLEMENTOR = "traffictypeimplementor";
|
||||||
public static final String KEYWORD = "keyword";
|
public static final String KEYWORD = "keyword";
|
||||||
public static final String LIST_ALL = "listall";
|
public static final String LIST_ALL = "listall";
|
||||||
|
public static final String LIST_SYSTEM_VMS = "listsystemvms";
|
||||||
public static final String IP_RANGES = "ipranges";
|
public static final String IP_RANGES = "ipranges";
|
||||||
public static final String IPV6_ROUTING = "ip6routing";
|
public static final String IPV6_ROUTING = "ip6routing";
|
||||||
public static final String IPV6_ROUTES = "ip6routes";
|
public static final String IPV6_ROUTES = "ip6routes";
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import org.apache.cloudstack.api.Parameter;
|
|||||||
import org.apache.cloudstack.api.ResponseObject.ResponseView;
|
import org.apache.cloudstack.api.ResponseObject.ResponseView;
|
||||||
import org.apache.cloudstack.api.ServerApiException;
|
import org.apache.cloudstack.api.ServerApiException;
|
||||||
import org.apache.cloudstack.api.response.HostResponse;
|
import org.apache.cloudstack.api.response.HostResponse;
|
||||||
|
import org.apache.cloudstack.api.response.SystemVmResponse;
|
||||||
import org.apache.cloudstack.api.response.UserVmResponse;
|
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||||
import org.apache.commons.collections.MapUtils;
|
import org.apache.commons.collections.MapUtils;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
@ -152,20 +153,17 @@ public class MigrateVirtualMachineWithVolumeCmd extends BaseAsyncCmd {
|
|||||||
@Override
|
@Override
|
||||||
public void execute() {
|
public void execute() {
|
||||||
if (hostId == null && MapUtils.isEmpty(migrateVolumeTo)) {
|
if (hostId == null && MapUtils.isEmpty(migrateVolumeTo)) {
|
||||||
throw new InvalidParameterValueException(String.format("Either %s or %s must be passed for migrating the VM", ApiConstants.HOST_ID, ApiConstants.MIGRATE_TO));
|
throw new InvalidParameterValueException(String.format("Either %s or %s must be passed for migrating the VM.", ApiConstants.HOST_ID, ApiConstants.MIGRATE_TO));
|
||||||
}
|
}
|
||||||
|
|
||||||
UserVm userVm = _userVmService.getUserVm(getVirtualMachineId());
|
VirtualMachine virtualMachine = _userVmService.getVm(getVirtualMachineId());
|
||||||
if (userVm == null) {
|
if (!VirtualMachine.State.Running.equals(virtualMachine.getState()) && hostId != null) {
|
||||||
throw new InvalidParameterValueException("Unable to find the VM by id=" + getVirtualMachineId());
|
throw new InvalidParameterValueException(String.format("%s is not in the Running state to migrate it to the new host.", virtualMachine));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!VirtualMachine.State.Running.equals(userVm.getState()) && hostId != null) {
|
if (!VirtualMachine.State.Stopped.equals(virtualMachine.getState()) && hostId == null) {
|
||||||
throw new InvalidParameterValueException(String.format("VM ID: %s is not in Running state to migrate it to new host", userVm.getUuid()));
|
throw new InvalidParameterValueException(String.format("%s is not in the Stopped state to migrate, use the %s parameter to migrate it to a new host.",
|
||||||
}
|
virtualMachine, ApiConstants.HOST_ID));
|
||||||
|
|
||||||
if (!VirtualMachine.State.Stopped.equals(userVm.getState()) && hostId == null) {
|
|
||||||
throw new InvalidParameterValueException(String.format("VM ID: %s is not in Stopped state to migrate, use %s parameter to migrate it to a new host", userVm.getUuid(), ApiConstants.HOST_ID));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -174,16 +172,15 @@ public class MigrateVirtualMachineWithVolumeCmd extends BaseAsyncCmd {
|
|||||||
Host destinationHost = _resourceService.getHost(getHostId());
|
Host destinationHost = _resourceService.getHost(getHostId());
|
||||||
// OfflineVmwareMigration: destination host would have to not be a required parameter for stopped VMs
|
// OfflineVmwareMigration: destination host would have to not be a required parameter for stopped VMs
|
||||||
if (destinationHost == null) {
|
if (destinationHost == null) {
|
||||||
throw new InvalidParameterValueException("Unable to find the host to migrate the VM, host id =" + getHostId());
|
s_logger.error(String.format("Unable to find the host with ID [%s].", getHostId()));
|
||||||
|
throw new InvalidParameterValueException("Unable to find the specified host to migrate the VM.");
|
||||||
}
|
}
|
||||||
migratedVm = _userVmService.migrateVirtualMachineWithVolume(getVirtualMachineId(), destinationHost, getVolumeToPool());
|
migratedVm = _userVmService.migrateVirtualMachineWithVolume(getVirtualMachineId(), destinationHost, getVolumeToPool());
|
||||||
} else if (MapUtils.isNotEmpty(migrateVolumeTo)) {
|
} else if (MapUtils.isNotEmpty(migrateVolumeTo)) {
|
||||||
migratedVm = _userVmService.vmStorageMigration(getVirtualMachineId(), getVolumeToPool());
|
migratedVm = _userVmService.vmStorageMigration(getVirtualMachineId(), getVolumeToPool());
|
||||||
}
|
}
|
||||||
if (migratedVm != null) {
|
if (migratedVm != null) {
|
||||||
UserVmResponse response = _responseGenerator.createUserVmResponse(ResponseView.Full, "virtualmachine", (UserVm)migratedVm).get(0);
|
setResponseBasedOnVmType(virtualMachine, migratedVm);
|
||||||
response.setResponseName(getCommandName());
|
|
||||||
setResponseObject(response);
|
|
||||||
} else {
|
} else {
|
||||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to migrate vm");
|
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to migrate vm");
|
||||||
}
|
}
|
||||||
@ -195,4 +192,16 @@ public class MigrateVirtualMachineWithVolumeCmd extends BaseAsyncCmd {
|
|||||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
|
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setResponseBasedOnVmType(VirtualMachine virtualMachine, VirtualMachine migratedVm) {
|
||||||
|
if (VirtualMachine.Type.User.equals(virtualMachine.getType())) {
|
||||||
|
UserVmResponse userVmResponse = _responseGenerator.createUserVmResponse(ResponseView.Full, "virtualmachine", (UserVm) migratedVm).get(0);
|
||||||
|
userVmResponse.setResponseName(getCommandName());
|
||||||
|
setResponseObject(userVmResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SystemVmResponse systemVmResponse = _responseGenerator.createSystemVmResponse(migratedVm);
|
||||||
|
systemVmResponse.setResponseName(getCommandName());
|
||||||
|
setResponseObject(systemVmResponse);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,6 +88,10 @@ public class ListVolumesCmd extends BaseListTaggedResourcesCmd implements UserCm
|
|||||||
RoleType.Admin})
|
RoleType.Admin})
|
||||||
private Boolean display;
|
private Boolean display;
|
||||||
|
|
||||||
|
@Parameter(name = ApiConstants.LIST_SYSTEM_VMS, type = CommandType.BOOLEAN, description = "list system VMs; only ROOT admin is eligible to pass this parameter", since = "4.18",
|
||||||
|
authorized = { RoleType.Admin })
|
||||||
|
private Boolean listSystemVms;
|
||||||
|
|
||||||
@Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "state of the volume. Possible values are: Ready, Allocated, Destroy, Expunging, Expunged.")
|
@Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "state of the volume. Possible values are: Ready, Allocated, Destroy, Expunging, Expunged.")
|
||||||
private String state;
|
private String state;
|
||||||
|
|
||||||
@ -135,6 +139,10 @@ public class ListVolumesCmd extends BaseListTaggedResourcesCmd implements UserCm
|
|||||||
return storageId;
|
return storageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getListSystemVms() {
|
||||||
|
return listSystemVms;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Boolean getDisplay() {
|
public Boolean getDisplay() {
|
||||||
if (display != null) {
|
if (display != null) {
|
||||||
|
|||||||
@ -96,6 +96,10 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
|
|||||||
@Param(description = "state of the virtual machine")
|
@Param(description = "state of the virtual machine")
|
||||||
private String virtualMachineState;
|
private String virtualMachineState;
|
||||||
|
|
||||||
|
@SerializedName(ApiConstants.VM_TYPE)
|
||||||
|
@Param(description = "type of the virtual machine")
|
||||||
|
private String vmType;
|
||||||
|
|
||||||
@SerializedName(ApiConstants.PROVISIONINGTYPE)
|
@SerializedName(ApiConstants.PROVISIONINGTYPE)
|
||||||
@Param(description = "provisioning type used to create volumes.")
|
@Param(description = "provisioning type used to create volumes.")
|
||||||
private String provisioningType;
|
private String provisioningType;
|
||||||
@ -333,6 +337,10 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
|
|||||||
this.volumeType = volumeType;
|
this.volumeType = volumeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setVmType(String vmType) {
|
||||||
|
this.vmType = vmType;
|
||||||
|
}
|
||||||
|
|
||||||
public void setDeviceId(Long deviceId) {
|
public void setDeviceId(Long deviceId) {
|
||||||
this.deviceId = deviceId;
|
this.deviceId = deviceId;
|
||||||
}
|
}
|
||||||
@ -666,6 +674,10 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getVmType() {
|
||||||
|
return vmType;
|
||||||
|
}
|
||||||
|
|
||||||
public String getAccountName() {
|
public String getAccountName() {
|
||||||
return accountName;
|
return accountName;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,226 @@
|
|||||||
|
// 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.admin.vm;
|
||||||
|
|
||||||
|
import com.cloud.exception.InvalidParameterValueException;
|
||||||
|
import com.cloud.exception.ManagementServerException;
|
||||||
|
import com.cloud.exception.ResourceUnavailableException;
|
||||||
|
import com.cloud.exception.VirtualMachineMigrationException;
|
||||||
|
import com.cloud.host.Host;
|
||||||
|
import com.cloud.resource.ResourceService;
|
||||||
|
import com.cloud.uservm.UserVm;
|
||||||
|
import com.cloud.utils.db.UUIDManager;
|
||||||
|
import com.cloud.vm.UserVmService;
|
||||||
|
import com.cloud.vm.VirtualMachine;
|
||||||
|
import org.apache.cloudstack.api.ApiConstants;
|
||||||
|
import org.apache.cloudstack.api.ResponseGenerator;
|
||||||
|
import org.apache.cloudstack.api.ResponseObject;
|
||||||
|
import org.apache.cloudstack.api.ServerApiException;
|
||||||
|
import org.apache.cloudstack.api.response.SystemVmResponse;
|
||||||
|
import org.apache.cloudstack.api.response.UserVmResponse;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.Spy;
|
||||||
|
import org.powermock.modules.junit4.PowerMockRunner;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RunWith(PowerMockRunner.class)
|
||||||
|
public class MigrateVirtualMachineWithVolumeCmdTest {
|
||||||
|
@Mock
|
||||||
|
UserVmService userVmServiceMock;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
UUIDManager uuidManagerMock;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
ResourceService resourceServiceMock;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
ResponseGenerator responseGeneratorMock;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
VirtualMachine virtualMachineMock;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
Host hostMock;
|
||||||
|
|
||||||
|
@Spy
|
||||||
|
@InjectMocks
|
||||||
|
MigrateVirtualMachineWithVolumeCmd cmdSpy = new MigrateVirtualMachineWithVolumeCmd();
|
||||||
|
|
||||||
|
private Long hostId = 1L;
|
||||||
|
private Long virtualMachineUuid = 1L;
|
||||||
|
private String virtualMachineName = "VM-name";
|
||||||
|
private Map<String, String> migrateVolumeTo = Map.of("key","value");
|
||||||
|
private SystemVmResponse systemVmResponse = new SystemVmResponse();
|
||||||
|
private UserVmResponse userVmResponse = new UserVmResponse();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
Mockito.when(cmdSpy.getVirtualMachineId()).thenReturn(virtualMachineUuid);
|
||||||
|
Mockito.when(cmdSpy.getHostId()).thenReturn(hostId);
|
||||||
|
Mockito.when(cmdSpy.getVolumeToPool()).thenReturn(migrateVolumeTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void executeTestHostIdIsNullAndMigrateVolumeToIsNullThrowsInvalidParameterValueException(){
|
||||||
|
ReflectionTestUtils.setField(cmdSpy, "hostId", null);
|
||||||
|
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
cmdSpy.execute();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Assert.assertEquals(InvalidParameterValueException.class, e.getClass());
|
||||||
|
String expected = String.format("Either %s or %s must be passed for migrating the VM.", ApiConstants.HOST_ID, ApiConstants.MIGRATE_TO);
|
||||||
|
Assert.assertEquals(expected , e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void executeTestVMIsStoppedAndHostIdIsNotNullThrowsInvalidParameterValueException(){
|
||||||
|
ReflectionTestUtils.setField(cmdSpy, "hostId", hostId);
|
||||||
|
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
|
||||||
|
|
||||||
|
Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
|
||||||
|
Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Stopped);
|
||||||
|
Mockito.when(virtualMachineMock.toString()).thenReturn(String.format("VM [uuid: %s, name: %s]", virtualMachineUuid, virtualMachineName));
|
||||||
|
|
||||||
|
try {
|
||||||
|
cmdSpy.execute();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Assert.assertEquals(InvalidParameterValueException.class, e.getClass());
|
||||||
|
String expected = String.format("%s is not in the Running state to migrate it to the new host.", virtualMachineMock);
|
||||||
|
Assert.assertEquals(expected , e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void executeTestVMIsRunningAndHostIdIsNullThrowsInvalidParameterValueException(){
|
||||||
|
ReflectionTestUtils.setField(cmdSpy, "hostId", null);
|
||||||
|
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
|
||||||
|
|
||||||
|
Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
|
||||||
|
Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Running);
|
||||||
|
Mockito.when(virtualMachineMock.toString()).thenReturn(String.format("VM [uuid: %s, name: %s]", virtualMachineUuid, virtualMachineName));
|
||||||
|
|
||||||
|
try {
|
||||||
|
cmdSpy.execute();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Assert.assertEquals(InvalidParameterValueException.class, e.getClass());
|
||||||
|
String expected = String.format("%s is not in the Stopped state to migrate, use the %s parameter to migrate it to a new host.", virtualMachineMock,
|
||||||
|
ApiConstants.HOST_ID);
|
||||||
|
Assert.assertEquals(expected , e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void executeTestHostIdIsNullThrowsInvalidParameterValueException(){
|
||||||
|
ReflectionTestUtils.setField(cmdSpy, "hostId", hostId);
|
||||||
|
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
|
||||||
|
|
||||||
|
Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
|
||||||
|
Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Running);
|
||||||
|
Mockito.when(resourceServiceMock.getHost(Mockito.anyLong())).thenReturn(null);
|
||||||
|
Mockito.when(uuidManagerMock.getUuid(Host.class, virtualMachineUuid)).thenReturn(virtualMachineUuid.toString());
|
||||||
|
|
||||||
|
try {
|
||||||
|
cmdSpy.execute();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Assert.assertEquals(InvalidParameterValueException.class, e.getClass());
|
||||||
|
String expected = "Unable to find the specified host to migrate the VM.";
|
||||||
|
Assert.assertEquals(expected , e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void executeTestHostIsNotNullMigratedVMIsNullThrowsServerApiException() throws ManagementServerException, ResourceUnavailableException, VirtualMachineMigrationException {
|
||||||
|
ReflectionTestUtils.setField(cmdSpy, "hostId", hostId);
|
||||||
|
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
|
||||||
|
|
||||||
|
Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
|
||||||
|
Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Running);
|
||||||
|
Mockito.when(resourceServiceMock.getHost(Mockito.anyLong())).thenReturn(hostMock);
|
||||||
|
Mockito.when(userVmServiceMock.migrateVirtualMachineWithVolume(virtualMachineUuid, hostMock, migrateVolumeTo)).thenReturn(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
cmdSpy.execute();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Assert.assertEquals(ServerApiException.class, e.getClass());
|
||||||
|
String expected = "Failed to migrate vm";
|
||||||
|
Assert.assertEquals(expected , e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void executeTestHostIsNullMigratedVMIsNullThrowsServerApiException() {
|
||||||
|
ReflectionTestUtils.setField(cmdSpy, "hostId", null);
|
||||||
|
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
|
||||||
|
|
||||||
|
Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
|
||||||
|
Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Stopped);
|
||||||
|
Mockito.when(userVmServiceMock.vmStorageMigration(virtualMachineUuid, migrateVolumeTo)).thenReturn(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
cmdSpy.execute();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Assert.assertEquals(ServerApiException.class, e.getClass());
|
||||||
|
String expected = "Failed to migrate vm";
|
||||||
|
Assert.assertEquals(expected , e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void executeTestSystemVMMigratedWithSuccess() {
|
||||||
|
ReflectionTestUtils.setField(cmdSpy, "hostId", null);
|
||||||
|
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
|
||||||
|
|
||||||
|
Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(virtualMachineMock);
|
||||||
|
Mockito.when(virtualMachineMock.getState()).thenReturn(VirtualMachine.State.Stopped);
|
||||||
|
Mockito.when(userVmServiceMock.vmStorageMigration(virtualMachineUuid, migrateVolumeTo)).thenReturn(virtualMachineMock);
|
||||||
|
Mockito.when(virtualMachineMock.getType()).thenReturn(VirtualMachine.Type.ConsoleProxy);
|
||||||
|
Mockito.when(responseGeneratorMock.createSystemVmResponse(virtualMachineMock)).thenReturn(systemVmResponse);
|
||||||
|
|
||||||
|
cmdSpy.execute();
|
||||||
|
|
||||||
|
Mockito.verify(responseGeneratorMock, Mockito.times(1)).createSystemVmResponse(virtualMachineMock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void executeTestUserVMMigratedWithSuccess() {
|
||||||
|
UserVm userVmMock = Mockito.mock(UserVm.class);
|
||||||
|
ReflectionTestUtils.setField(cmdSpy, "hostId", null);
|
||||||
|
ReflectionTestUtils.setField(cmdSpy, "migrateVolumeTo", migrateVolumeTo);
|
||||||
|
|
||||||
|
Mockito.when(userVmServiceMock.getVm(Mockito.anyLong())).thenReturn(userVmMock);
|
||||||
|
Mockito.when(userVmMock.getState()).thenReturn(VirtualMachine.State.Stopped);
|
||||||
|
Mockito.when(userVmServiceMock.vmStorageMigration(virtualMachineUuid, migrateVolumeTo)).thenReturn(userVmMock);
|
||||||
|
Mockito.when(userVmMock.getType()).thenReturn(VirtualMachine.Type.User);
|
||||||
|
Mockito.when(responseGeneratorMock.createUserVmResponse(ResponseObject.ResponseView.Full, "virtualmachine", userVmMock)).thenReturn(List.of(userVmResponse));
|
||||||
|
|
||||||
|
cmdSpy.execute();
|
||||||
|
|
||||||
|
Mockito.verify(responseGeneratorMock, Mockito.times(1)).createUserVmResponse(ResponseObject.ResponseView.Full, "virtualmachine", userVmMock);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2082,6 +2082,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
|||||||
Long diskOffId = cmd.getDiskOfferingId();
|
Long diskOffId = cmd.getDiskOfferingId();
|
||||||
Boolean display = cmd.getDisplay();
|
Boolean display = cmd.getDisplay();
|
||||||
String state = cmd.getState();
|
String state = cmd.getState();
|
||||||
|
boolean shouldListSystemVms = shouldListSystemVms(cmd, caller.getId());
|
||||||
|
|
||||||
Long zoneId = cmd.getZoneId();
|
Long zoneId = cmd.getZoneId();
|
||||||
Long podId = cmd.getPodId();
|
Long podId = cmd.getPodId();
|
||||||
@ -2126,14 +2127,16 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
|||||||
sb.and("display", sb.entity().isDisplayVolume(), SearchCriteria.Op.EQ);
|
sb.and("display", sb.entity().isDisplayVolume(), SearchCriteria.Op.EQ);
|
||||||
sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
|
sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
|
||||||
sb.and("stateNEQ", sb.entity().getState(), SearchCriteria.Op.NEQ);
|
sb.and("stateNEQ", sb.entity().getState(), SearchCriteria.Op.NEQ);
|
||||||
|
|
||||||
|
if (!shouldListSystemVms) {
|
||||||
sb.and().op("systemUse", sb.entity().isSystemUse(), SearchCriteria.Op.NEQ);
|
sb.and().op("systemUse", sb.entity().isSystemUse(), SearchCriteria.Op.NEQ);
|
||||||
sb.or("nulltype", sb.entity().isSystemUse(), SearchCriteria.Op.NULL);
|
sb.or("nulltype", sb.entity().isSystemUse(), SearchCriteria.Op.NULL);
|
||||||
sb.cp();
|
sb.cp();
|
||||||
|
|
||||||
// display UserVM volumes only
|
|
||||||
sb.and().op("type", sb.entity().getVmType(), SearchCriteria.Op.NIN);
|
sb.and().op("type", sb.entity().getVmType(), SearchCriteria.Op.NIN);
|
||||||
sb.or("nulltype", sb.entity().getVmType(), SearchCriteria.Op.NULL);
|
sb.or("nulltype", sb.entity().getVmType(), SearchCriteria.Op.NULL);
|
||||||
sb.cp();
|
sb.cp();
|
||||||
|
}
|
||||||
|
|
||||||
// now set the SC criteria...
|
// now set the SC criteria...
|
||||||
SearchCriteria<VolumeJoinVO> sc = sb.create();
|
SearchCriteria<VolumeJoinVO> sc = sb.create();
|
||||||
@ -2158,7 +2161,10 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
|||||||
|
|
||||||
setIdsListToSearchCriteria(sc, ids);
|
setIdsListToSearchCriteria(sc, ids);
|
||||||
|
|
||||||
|
if (!shouldListSystemVms) {
|
||||||
sc.setParameters("systemUse", 1);
|
sc.setParameters("systemUse", 1);
|
||||||
|
sc.setParameters("type", VirtualMachine.Type.ConsoleProxy, VirtualMachine.Type.SecondaryStorageVm, VirtualMachine.Type.DomainRouter);
|
||||||
|
}
|
||||||
|
|
||||||
if (tags != null && !tags.isEmpty()) {
|
if (tags != null && !tags.isEmpty()) {
|
||||||
SearchCriteria<VolumeJoinVO> tagSc = _volumeJoinDao.createSearchCriteria();
|
SearchCriteria<VolumeJoinVO> tagSc = _volumeJoinDao.createSearchCriteria();
|
||||||
@ -2206,8 +2212,6 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
|||||||
if (clusterId != null) {
|
if (clusterId != null) {
|
||||||
sc.setParameters("clusterId", clusterId);
|
sc.setParameters("clusterId", clusterId);
|
||||||
}
|
}
|
||||||
// Don't return DomR and ConsoleProxy volumes
|
|
||||||
sc.setParameters("type", VirtualMachine.Type.ConsoleProxy, VirtualMachine.Type.SecondaryStorageVm, VirtualMachine.Type.DomainRouter);
|
|
||||||
|
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
sc.setParameters("state", state);
|
sc.setParameters("state", state);
|
||||||
@ -2232,6 +2236,10 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
|
|||||||
return new Pair<List<VolumeJoinVO>, Integer>(vrs, count);
|
return new Pair<List<VolumeJoinVO>, Integer>(vrs, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean shouldListSystemVms(ListVolumesCmd cmd, Long callerId) {
|
||||||
|
return Boolean.TRUE.equals(cmd.getListSystemVms()) && _accountMgr.isRootAdmin(callerId);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListResponse<DomainResponse> searchForDomains(ListDomainsCmd cmd) {
|
public ListResponse<DomainResponse> searchForDomains(ListDomainsCmd cmd) {
|
||||||
Pair<List<DomainJoinVO>, Integer> result = searchForDomainsInternal(cmd);
|
Pair<List<DomainJoinVO>, Integer> result = searchForDomainsInternal(cmd);
|
||||||
|
|||||||
@ -98,6 +98,10 @@ public class VolumeJoinDaoImpl extends GenericDaoBaseWithTagInformation<VolumeJo
|
|||||||
volResponse.setPodName(volume.getPodName());
|
volResponse.setPodName(volume.getPodName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (volume.getVmType() != null) {
|
||||||
|
volResponse.setVmType(volume.getVmType().toString());
|
||||||
|
}
|
||||||
|
|
||||||
if (volume.getVolumeType() != null) {
|
if (volume.getVolumeType() != null) {
|
||||||
volResponse.setVolumeType(volume.getVolumeType().toString());
|
volResponse.setVolumeType(volume.getVolumeType().toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6288,6 +6288,11 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
|
|||||||
return _vmDao.findById(vmId);
|
return _vmDao.findById(vmId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VirtualMachine getVm(long vmId) {
|
||||||
|
return _vmInstanceDao.findById(vmId);
|
||||||
|
}
|
||||||
|
|
||||||
private VMInstanceVO preVmStorageMigrationCheck(Long vmId) {
|
private VMInstanceVO preVmStorageMigrationCheck(Long vmId) {
|
||||||
// access check - only root admin can migrate VM
|
// access check - only root admin can migrate VM
|
||||||
Account caller = CallContext.current().getCallingAccount();
|
Account caller = CallContext.current().getCallingAccount();
|
||||||
|
|||||||
@ -370,7 +370,7 @@
|
|||||||
<div class="resource-detail-item__label">{{ $t('label.vmname') }}</div>
|
<div class="resource-detail-item__label">{{ $t('label.vmname') }}</div>
|
||||||
<div class="resource-detail-item__details">
|
<div class="resource-detail-item__details">
|
||||||
<desktop-outlined />
|
<desktop-outlined />
|
||||||
<router-link :to="{ path: '/vm/' + resource.virtualmachineid }">{{ resource.vmname || resource.vm || resource.virtualmachinename || resource.virtualmachineid }} </router-link>
|
<router-link :to="{ path: createPathBasedOnVmType(resource.vmtype, resource.virtualmachineid) }">{{ resource.vmname || resource.vm || resource.virtualmachinename || resource.virtualmachineid }} </router-link>
|
||||||
<status class="status status--end" :text="resource.vmstate" v-if="resource.vmstate"/>
|
<status class="status status--end" :text="resource.vmstate" v-if="resource.vmstate"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -705,6 +705,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { api } from '@/api'
|
import { api } from '@/api'
|
||||||
|
import { createPathBasedOnVmType } from '@/utils/plugins'
|
||||||
import Console from '@/components/widgets/Console'
|
import Console from '@/components/widgets/Console'
|
||||||
import OsLogo from '@/components/widgets/OsLogo'
|
import OsLogo from '@/components/widgets/OsLogo'
|
||||||
import Status from '@/components/widgets/Status'
|
import Status from '@/components/widgets/Status'
|
||||||
@ -837,6 +838,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
createPathBasedOnVmType: createPathBasedOnVmType,
|
||||||
updateResourceAdditionalData () {
|
updateResourceAdditionalData () {
|
||||||
if (!this.resource) return
|
if (!this.resource) return
|
||||||
this.resourceType = this.$route.meta.resourceType
|
this.resourceType = this.$route.meta.resourceType
|
||||||
|
|||||||
@ -168,7 +168,7 @@
|
|||||||
{{ text }}
|
{{ text }}
|
||||||
</template>
|
</template>
|
||||||
<template #vmname="{ text, record }">
|
<template #vmname="{ text, record }">
|
||||||
<router-link :to="{ path: '/vm/' + record.virtualmachineid }">{{ text }}</router-link>
|
<router-link :to="{ path: createPathBasedOnVmType(record.vmtype, record.virtualmachineid) }">{{ text }}</router-link>
|
||||||
</template>
|
</template>
|
||||||
<template #virtualmachinename="{ text, record }">
|
<template #virtualmachinename="{ text, record }">
|
||||||
<router-link :to="{ path: '/vm/' + record.virtualmachineid }">{{ text }}</router-link>
|
<router-link :to="{ path: '/vm/' + record.virtualmachineid }">{{ text }}</router-link>
|
||||||
@ -419,6 +419,7 @@ import QuickView from '@/components/view/QuickView'
|
|||||||
import TooltipButton from '@/components/widgets/TooltipButton'
|
import TooltipButton from '@/components/widgets/TooltipButton'
|
||||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||||
import ResourceLabel from '@/components/widgets/ResourceLabel'
|
import ResourceLabel from '@/components/widgets/ResourceLabel'
|
||||||
|
import { createPathBasedOnVmType } from '@/utils/plugins'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ListView',
|
name: 'ListView',
|
||||||
@ -513,6 +514,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
createPathBasedOnVmType: createPathBasedOnVmType,
|
||||||
quickViewEnabled () {
|
quickViewEnabled () {
|
||||||
return new RegExp(['/vm', '/kubernetes', '/ssh', '/userdata', '/vmgroup', '/affinitygroup',
|
return new RegExp(['/vm', '/kubernetes', '/ssh', '/userdata', '/vmgroup', '/affinitygroup',
|
||||||
'/volume', '/snapshot', '/vmsnapshot', '/backup',
|
'/volume', '/snapshot', '/vmsnapshot', '/backup',
|
||||||
|
|||||||
125
ui/src/components/view/VolumesTab.vue
Normal file
125
ui/src/components/view/VolumesTab.vue
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<a-table
|
||||||
|
class="table"
|
||||||
|
size="small"
|
||||||
|
:columns="volumeColumns"
|
||||||
|
:dataSource="volumes"
|
||||||
|
:rowKey="item => item.id"
|
||||||
|
:pagination="false"
|
||||||
|
>
|
||||||
|
<template #name="{ text, record }">
|
||||||
|
<hdd-outlined style="margin-right: 5px"/>
|
||||||
|
<router-link :to="{ path: '/volume/' + record.id }" style="margin-right: 5px">
|
||||||
|
{{ text }}
|
||||||
|
</router-link>
|
||||||
|
<a-tag v-if="record.provisioningtype">
|
||||||
|
{{ record.provisioningtype }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
<template #state="{ text }">
|
||||||
|
<status :text="text ? text : ''" />{{ text }}
|
||||||
|
</template>
|
||||||
|
<template #size="{ record }">
|
||||||
|
{{ parseFloat(record.size / (1024.0 * 1024.0 * 1024.0)).toFixed(2) }} GB
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { api } from '@/api'
|
||||||
|
import Status from '@/components/widgets/Status'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VolumesTab',
|
||||||
|
components: {
|
||||||
|
Status
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
resource: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inject: ['parentFetchData'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
vm: {},
|
||||||
|
volumes: [],
|
||||||
|
volumeColumns: [
|
||||||
|
{
|
||||||
|
title: this.$t('label.name'),
|
||||||
|
dataIndex: 'name',
|
||||||
|
slots: { customRender: 'name' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$t('label.state'),
|
||||||
|
dataIndex: 'state',
|
||||||
|
slots: { customRender: 'state' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$t('label.type'),
|
||||||
|
dataIndex: 'type'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: this.$t('label.size'),
|
||||||
|
dataIndex: 'size',
|
||||||
|
slots: { customRender: 'size' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.vm = this.resource
|
||||||
|
this.fetchData()
|
||||||
|
console.log(this.resource.volumes)
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
resource: function (newItem) {
|
||||||
|
this.vm = newItem
|
||||||
|
this.fetchData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchData () {
|
||||||
|
this.volumes = []
|
||||||
|
if (!this.vm?.id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.items.length) {
|
||||||
|
this.volumes = this.items
|
||||||
|
} else {
|
||||||
|
this.getVolumes()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getVolumes () {
|
||||||
|
api('listVolumes', { listall: true, listsystemvms: true, virtualmachineid: this.vm.id }).then(json => {
|
||||||
|
this.volumes = json.listvolumesresponse.volume
|
||||||
|
if (this.volumes) {
|
||||||
|
this.volumes.sort((a, b) => { return a.deviceid - b.deviceid })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -43,6 +43,9 @@ export default {
|
|||||||
name: 'router.health.checks',
|
name: 'router.health.checks',
|
||||||
show: (record, route, user) => { return ['Running'].includes(record.state) && ['Admin'].includes(user.roletype) },
|
show: (record, route, user) => { return ['Running'].includes(record.state) && ['Admin'].includes(user.roletype) },
|
||||||
component: shallowRef(defineAsyncComponent(() => import('@views/infra/routers/RouterHealthCheck.vue')))
|
component: shallowRef(defineAsyncComponent(() => import('@views/infra/routers/RouterHealthCheck.vue')))
|
||||||
|
}, {
|
||||||
|
name: 'volume',
|
||||||
|
component: shallowRef(defineAsyncComponent(() => import('@/components/view/VolumesTab.vue')))
|
||||||
}, {
|
}, {
|
||||||
name: 'events',
|
name: 'events',
|
||||||
resourceType: 'DomainRouter',
|
resourceType: 'DomainRouter',
|
||||||
|
|||||||
@ -32,6 +32,10 @@ export default {
|
|||||||
name: 'details',
|
name: 'details',
|
||||||
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
|
component: shallowRef(defineAsyncComponent(() => import('@/components/view/DetailsTab.vue')))
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'volume',
|
||||||
|
component: shallowRef(defineAsyncComponent(() => import('@/components/view/VolumesTab.vue')))
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'events',
|
name: 'events',
|
||||||
resourceType: 'SystemVm',
|
resourceType: 'SystemVm',
|
||||||
|
|||||||
@ -485,3 +485,20 @@ export const genericUtilPlugin = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createPathBasedOnVmType (vmtype, virtualmachineid) {
|
||||||
|
let path = ''
|
||||||
|
switch (vmtype) {
|
||||||
|
case 'ConsoleProxy':
|
||||||
|
case 'SecondaryStorageVm':
|
||||||
|
path = '/systemvm/'
|
||||||
|
break
|
||||||
|
case 'DomainRouter':
|
||||||
|
path = '/router/'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
path = '/vm/'
|
||||||
|
}
|
||||||
|
|
||||||
|
return path + virtualmachineid
|
||||||
|
}
|
||||||
|
|||||||
@ -847,6 +847,10 @@ export default {
|
|||||||
delete params.showunique
|
delete params.showunique
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (['Admin'].includes(this.$store.getters.userInfo.roletype) && ['listVolumesMetrics', 'listVolumes'].includes(this.apiName)) {
|
||||||
|
params.listsystemvms = true
|
||||||
|
}
|
||||||
|
|
||||||
this.loading = true
|
this.loading = true
|
||||||
if (this.$route.params && this.$route.params.id) {
|
if (this.$route.params && this.$route.params.id) {
|
||||||
params.id = this.$route.params.id
|
params.id = this.$route.params.id
|
||||||
|
|||||||
@ -33,31 +33,8 @@
|
|||||||
<router-link :to="{ path: '/iso/' + vm.isoid }">{{ vm.isoname }}</router-link> <br/>
|
<router-link :to="{ path: '/iso/' + vm.isoid }">{{ vm.isoname }}</router-link> <br/>
|
||||||
<barcode-outlined /> {{ vm.isoid }}
|
<barcode-outlined /> {{ vm.isoid }}
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane :tab="$t('label.volumes')" key="volumes" v-if="'listVolumes' in $store.getters.apis">
|
<a-tab-pane :tab="$t('label.volumes')" key="volumes">
|
||||||
<a-table
|
<volumes-tab :resource="vm" :items="volumes" :loading="loading" />
|
||||||
class="table"
|
|
||||||
size="small"
|
|
||||||
:columns="volumeColumns"
|
|
||||||
:dataSource="volumes"
|
|
||||||
:rowKey="item => item.id"
|
|
||||||
:pagination="false"
|
|
||||||
>
|
|
||||||
<template #name="{ text, record }">
|
|
||||||
<hdd-outlined />
|
|
||||||
<router-link :to="{ path: '/volume/' + record.id }">
|
|
||||||
{{ text }}
|
|
||||||
</router-link>
|
|
||||||
<a-tag v-if="record.provisioningtype">
|
|
||||||
{{ record.provisioningtype }}
|
|
||||||
</a-tag>
|
|
||||||
</template>
|
|
||||||
<template #state="{ text }">
|
|
||||||
<status :text="text ? text : ''" />{{ text }}
|
|
||||||
</template>
|
|
||||||
<template #size="{ record }">
|
|
||||||
{{ parseFloat(record.size / (1024.0 * 1024.0 * 1024.0)).toFixed(2) }} GB
|
|
||||||
</template>
|
|
||||||
</a-table>
|
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane :tab="$t('label.nics')" key="nics" v-if="'listNics' in $store.getters.apis">
|
<a-tab-pane :tab="$t('label.nics')" key="nics" v-if="'listNics' in $store.getters.apis">
|
||||||
<a-button
|
<a-button
|
||||||
@ -308,7 +285,6 @@
|
|||||||
import { api } from '@/api'
|
import { api } from '@/api'
|
||||||
import { mixinDevice } from '@/utils/mixin.js'
|
import { mixinDevice } from '@/utils/mixin.js'
|
||||||
import ResourceLayout from '@/layouts/ResourceLayout'
|
import ResourceLayout from '@/layouts/ResourceLayout'
|
||||||
import Status from '@/components/widgets/Status'
|
|
||||||
import DetailsTab from '@/components/view/DetailsTab'
|
import DetailsTab from '@/components/view/DetailsTab'
|
||||||
import StatsTab from '@/components/view/StatsTab'
|
import StatsTab from '@/components/view/StatsTab'
|
||||||
import EventsTab from '@/components/view/EventsTab'
|
import EventsTab from '@/components/view/EventsTab'
|
||||||
@ -318,6 +294,7 @@ import ListResourceTable from '@/components/view/ListResourceTable'
|
|||||||
import TooltipButton from '@/components/widgets/TooltipButton'
|
import TooltipButton from '@/components/widgets/TooltipButton'
|
||||||
import ResourceIcon from '@/components/view/ResourceIcon'
|
import ResourceIcon from '@/components/view/ResourceIcon'
|
||||||
import AnnotationsTab from '@/components/view/AnnotationsTab'
|
import AnnotationsTab from '@/components/view/AnnotationsTab'
|
||||||
|
import VolumesTab from '@/components/view/VolumesTab.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'InstanceTab',
|
name: 'InstanceTab',
|
||||||
@ -328,11 +305,11 @@ export default {
|
|||||||
EventsTab,
|
EventsTab,
|
||||||
DetailSettings,
|
DetailSettings,
|
||||||
NicsTable,
|
NicsTable,
|
||||||
Status,
|
|
||||||
ListResourceTable,
|
ListResourceTable,
|
||||||
TooltipButton,
|
TooltipButton,
|
||||||
ResourceIcon,
|
ResourceIcon,
|
||||||
AnnotationsTab
|
AnnotationsTab,
|
||||||
|
VolumesTab
|
||||||
},
|
},
|
||||||
mixins: [mixinDevice],
|
mixins: [mixinDevice],
|
||||||
props: {
|
props: {
|
||||||
@ -349,7 +326,6 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
vm: {},
|
vm: {},
|
||||||
volumes: [],
|
|
||||||
totalStorage: 0,
|
totalStorage: 0,
|
||||||
currentTab: 'details',
|
currentTab: 'details',
|
||||||
showAddNetworkModal: false,
|
showAddNetworkModal: false,
|
||||||
@ -367,27 +343,6 @@ export default {
|
|||||||
secondaryIPs: [],
|
secondaryIPs: [],
|
||||||
selectedNicId: '',
|
selectedNicId: '',
|
||||||
newSecondaryIp: '',
|
newSecondaryIp: '',
|
||||||
volumeColumns: [
|
|
||||||
{
|
|
||||||
title: this.$t('label.name'),
|
|
||||||
dataIndex: 'name',
|
|
||||||
slots: { customRender: 'name' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: this.$t('label.state'),
|
|
||||||
dataIndex: 'state',
|
|
||||||
slots: { customRender: 'state' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: this.$t('label.type'),
|
|
||||||
dataIndex: 'type'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: this.$t('label.size'),
|
|
||||||
dataIndex: 'size',
|
|
||||||
slots: { customRender: 'size' }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
editNicResource: {},
|
editNicResource: {},
|
||||||
listIps: {
|
listIps: {
|
||||||
loading: false,
|
loading: false,
|
||||||
@ -443,18 +398,10 @@ export default {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
fetchData () {
|
fetchData () {
|
||||||
this.volumes = []
|
|
||||||
this.annotations = []
|
this.annotations = []
|
||||||
if (!this.vm || !this.vm.id) {
|
if (!this.vm || !this.vm.id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
api('listVolumes', { listall: true, virtualmachineid: this.vm.id }).then(json => {
|
|
||||||
this.volumes = json.listvolumesresponse.volume
|
|
||||||
if (this.volumes) {
|
|
||||||
this.volumes.sort((a, b) => { return a.deviceid - b.deviceid })
|
|
||||||
}
|
|
||||||
this.dataResource.volumes = this.volumes
|
|
||||||
})
|
|
||||||
api('listAnnotations', { entityid: this.dataResource.id, entitytype: 'VM', annotationfilter: 'all' }).then(json => {
|
api('listAnnotations', { entityid: this.dataResource.id, entitytype: 'VM', annotationfilter: 'all' }).then(json => {
|
||||||
if (json.listannotationsresponse && json.listannotationsresponse.annotation) {
|
if (json.listannotationsresponse && json.listannotationsresponse.annotation) {
|
||||||
this.annotations = json.listannotationsresponse.annotation
|
this.annotations = json.listannotationsresponse.annotation
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user