diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index 06272cadae2..528a8b0c735 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.api.command.user.vm; import java.util.Objects; import java.util.stream.Stream; +import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.api.ACL; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -39,7 +40,6 @@ import com.cloud.exception.InsufficientServerCapacityException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.uservm.UserVm; -import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachine; @APICommand(name = "deployVirtualMachine", description = "Creates and automatically starts an Instance based on a service offering, disk offering, and Template.", responseObject = UserVmResponse.class, responseView = ResponseView.Restricted, entityType = {VirtualMachine.class}, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java index e0da954d381..115b37d61d1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java @@ -21,6 +21,7 @@ import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.user.Account; import com.cloud.uservm.UserVm; +import com.cloud.utils.StringUtils; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.Dhcp; import com.cloud.vm.VirtualMachine; @@ -44,7 +45,6 @@ import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.vm.lease.VMLeaseManager; import org.apache.commons.lang3.EnumUtils; -import org.apache.commons.lang3.StringUtils; import java.util.Collection; import java.util.HashMap; @@ -174,6 +174,9 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction, ///////////////////////////////////////////////////// public String getDisplayName() { + if (StringUtils.isBlank(displayName)) { + displayName = name; + } return displayName; } diff --git a/core/src/main/java/org/apache/cloudstack/storage/to/SnapshotObjectTO.java b/core/src/main/java/org/apache/cloudstack/storage/to/SnapshotObjectTO.java index 0c3bb99e75c..7643f80bbaa 100644 --- a/core/src/main/java/org/apache/cloudstack/storage/to/SnapshotObjectTO.java +++ b/core/src/main/java/org/apache/cloudstack/storage/to/SnapshotObjectTO.java @@ -48,9 +48,7 @@ public class SnapshotObjectTO extends DownloadableObjectTO implements DataTO { private Long physicalSize = (long) 0; private long accountId; - public SnapshotObjectTO() { - } @Override diff --git a/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java b/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java index 79a4bd6d6d8..292bafefbb6 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java @@ -606,7 +606,7 @@ public class SystemVmTemplateRegistration { template.setBits(64); template.setAccountId(Account.ACCOUNT_ID_SYSTEM); template.setUrl(details.getUrl()); - template.setChecksum(details.getChecksum()); + template.setChecksum(DigestHelper.prependAlgorithm(details.getChecksum())); template.setEnablePassword(false); template.setDisplayText(details.getName()); template.setFormat(details.getFormat()); @@ -1079,7 +1079,7 @@ public class SystemVmTemplateRegistration { protected void updateTemplateUrlChecksumAndGuestOsId(VMTemplateVO templateVO, MetadataTemplateDetails templateDetails) { templateVO.setUrl(templateDetails.getUrl()); - templateVO.setChecksum(templateDetails.getChecksum()); + templateVO.setChecksum(DigestHelper.prependAlgorithm(templateDetails.getChecksum())); GuestOSVO guestOS = guestOSDao.findOneByDisplayName(templateDetails.getGuestOs()); if (guestOS != null) { templateVO.setGuestOSId(guestOS.getId()); diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotObject.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotObject.java index 8264fcd4286..6a8bbd93ca4 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotObject.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotObject.java @@ -398,13 +398,16 @@ public class SnapshotObject implements SnapshotInfo { if (answer instanceof CreateObjectAnswer) { SnapshotObjectTO snapshotTO = (SnapshotObjectTO)((CreateObjectAnswer)answer).getData(); snapshotStore.setInstallPath(snapshotTO.getPath()); + if (snapshotTO.getPhysicalSize() != null && snapshotTO.getPhysicalSize() > 0L) { + snapshotStore.setPhysicalSize(snapshotTO.getPhysicalSize()); + } snapshotStoreDao.update(snapshotStore.getId(), snapshotStore); } else if (answer instanceof CopyCmdAnswer) { SnapshotObjectTO snapshotTO = (SnapshotObjectTO)((CopyCmdAnswer)answer).getNewData(); snapshotStore.setInstallPath(snapshotTO.getPath()); if (snapshotTO.getPhysicalSize() != null) { // For S3 delta snapshot, physical size is currently not set - snapshotStore.setPhysicalSize(snapshotTO.getPhysicalSize()); + snapshotStore.setPhysicalSize(snapshotTO.getPhysicalSize()); } if (snapshotTO.getParentSnapshotPath() == null) { snapshotStore.setParentSnapshotId(0L); diff --git a/extensions/HyperV/hyperv.py b/extensions/HyperV/hyperv.py index c9b1d4da77e..8e2858d3cae 100755 --- a/extensions/HyperV/hyperv.py +++ b/extensions/HyperV/hyperv.py @@ -210,6 +210,29 @@ class HyperVManager: power_state = "poweroff" succeed({"status": "success", "power_state": power_state}) + def statuses(self): + command = 'Get-VM | Select-Object Name, State | ConvertTo-Json' + output = self.run_ps(command) + if not output or output.strip() in ("", "null"): + vms = [] + else: + try: + vms = json.loads(output) + except json.JSONDecodeError: + fail("Failed to parse VM status output: " + output) + power_state = {} + if isinstance(vms, dict): + vms = [vms] + for vm in vms: + state = vm["State"].strip().lower() + if state == "running": + power_state[vm["Name"]] = "poweron" + elif state == "off": + power_state[vm["Name"]] = "poweroff" + else: + power_state[vm["Name"]] = "unknown" + succeed({"status": "success", "power_state": power_state}) + def delete(self): try: self.run_ps_int(f'Remove-VM -Name "{self.data["vmname"]}" -Force') @@ -286,6 +309,7 @@ def main(): "reboot": manager.reboot, "delete": manager.delete, "status": manager.status, + "statuses": manager.statuses, "getconsole": manager.get_console, "suspend": manager.suspend, "resume": manager.resume, diff --git a/extensions/Proxmox/proxmox.sh b/extensions/Proxmox/proxmox.sh index fc27f2f3075..459b744f44d 100755 --- a/extensions/Proxmox/proxmox.sh +++ b/extensions/Proxmox/proxmox.sh @@ -64,7 +64,7 @@ parse_json() { token="${host_token:-$extension_token}" secret="${host_secret:-$extension_secret}" - check_required_fields vm_internal_name url user token secret node + check_required_fields url user token secret node } urlencode() { @@ -206,6 +206,10 @@ prepare() { create() { if [[ -z "$vm_name" ]]; then + if [[ -z "$vm_internal_name" ]]; then + echo '{"error":"Missing required fields: vm_internal_name"}' + exit 1 + fi vm_name="$vm_internal_name" fi validate_name "VM" "$vm_name" @@ -331,71 +335,102 @@ get_node_host() { echo "$host" } - get_console() { - check_required_fields node vmid +get_console() { + check_required_fields node vmid - local api_resp port ticket - if ! api_resp="$(call_proxmox_api POST "/nodes/${node}/qemu/${vmid}/vncproxy")"; then - echo "$api_resp" | jq -c '{status:"error", error:(.errors.curl // (.errors|tostring))}' - exit 1 - fi + local api_resp port ticket + if ! api_resp="$(call_proxmox_api POST "/nodes/${node}/qemu/${vmid}/vncproxy")"; then + echo "$api_resp" | jq -c '{status:"error", error:(.errors.curl // (.errors|tostring))}' + exit 1 + fi - port="$(echo "$api_resp" | jq -re '.data.port // empty' 2>/dev/null || true)" - ticket="$(echo "$api_resp" | jq -re '.data.ticket // empty' 2>/dev/null || true)" + port="$(echo "$api_resp" | jq -re '.data.port // empty' 2>/dev/null || true)" + ticket="$(echo "$api_resp" | jq -re '.data.ticket // empty' 2>/dev/null || true)" - if [[ -z "$port" || -z "$ticket" ]]; then - jq -n --arg raw "$api_resp" \ - '{status:"error", error:"Proxmox response missing port/ticket", upstream:$raw}' - exit 1 - fi + if [[ -z "$port" || -z "$ticket" ]]; then + jq -n --arg raw "$api_resp" \ + '{status:"error", error:"Proxmox response missing port/ticket", upstream:$raw}' + exit 1 + fi - # Derive host from node’s network info - local host - host="$(get_node_host)" - if [[ -z "$host" ]]; then - jq -n --arg msg "Could not determine host IP for node $node" \ - '{status:"error", error:$msg}' - exit 1 - fi + # Derive host from node’s network info + local host + host="$(get_node_host)" + if [[ -z "$host" ]]; then + jq -n --arg msg "Could not determine host IP for node $node" \ + '{status:"error", error:$msg}' + exit 1 + fi - jq -n \ - --arg host "$host" \ - --arg port "$port" \ - --arg password "$ticket" \ - --argjson passwordonetimeuseonly true \ - '{ - status: "success", - message: "Console retrieved", - console: { - host: $host, - port: $port, - password: $password, - passwordonetimeuseonly: $passwordonetimeuseonly, - protocol: "vnc" - } - }' - } + jq -n \ + --arg host "$host" \ + --arg port "$port" \ + --arg password "$ticket" \ + --argjson passwordonetimeuseonly true \ + '{ + status: "success", + message: "Console retrieved", + console: { + host: $host, + port: $port, + password: $password, + passwordonetimeuseonly: $passwordonetimeuseonly, + protocol: "vnc" + } + }' +} + +statuses() { + local response + response=$(call_proxmox_api GET "/nodes/${node}/qemu") + + if [[ -z "$response" ]]; then + echo '{"status":"error","message":"empty response from Proxmox API"}' + return 1 + fi + + if ! echo "$response" | jq empty >/dev/null 2>&1; then + echo '{"status":"error","message":"invalid JSON response from Proxmox API"}' + return 1 + fi + + echo "$response" | jq -c ' + def map_state(s): + if s=="running" then "poweron" + elif s=="stopped" then "poweroff" + else "unknown" end; + + { + status: "success", + power_state: ( + .data + | map(select(.template != 1)) + | map({ ( (.name // (.vmid|tostring)) ): map_state(.status) }) + | add // {} + ) + }' +} list_snapshots() { snapshot_response=$(call_proxmox_api GET "/nodes/${node}/qemu/${vmid}/snapshot") echo "$snapshot_response" | jq ' - def to_date: - if . == "-" then "-" - elif . == null then "-" - else (. | tonumber | strftime("%Y-%m-%d %H:%M:%S")) - end; + def to_date: + if . == "-" then "-" + elif . == null then "-" + else (. | tonumber | strftime("%Y-%m-%d %H:%M:%S")) + end; - { - status: "success", - printmessage: "true", - message: [.data[] | { - name: .name, - snaptime: ((.snaptime // "-") | to_date), - description: .description, - parent: (.parent // "-"), - vmstate: (.vmstate // "-") - }] - } + { + status: "success", + printmessage: "true", + message: [.data[] | { + name: .name, + snaptime: ((.snaptime // "-") | to_date), + description: .description, + parent: (.parent // "-"), + vmstate: (.vmstate // "-") + }] + } ' } @@ -463,9 +498,9 @@ parse_json "$parameters" || exit 1 cleanup_vm=0 cleanup() { - if (( cleanup_vm == 1 )); then - execute_and_wait DELETE "/nodes/${node}/qemu/${vmid}" - fi + if (( cleanup_vm == 1 )); then + execute_and_wait DELETE "/nodes/${node}/qemu/${vmid}" + fi } trap cleanup EXIT @@ -492,6 +527,9 @@ case $action in status) status ;; + statuses) + statuses + ;; getconsole) get_console ;; diff --git a/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/hypervisor/external/provisioner/ExternalPathPayloadProvisioner.java b/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/hypervisor/external/provisioner/ExternalPathPayloadProvisioner.java index 92205b13c6f..fa3f4de5026 100644 --- a/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/hypervisor/external/provisioner/ExternalPathPayloadProvisioner.java +++ b/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/hypervisor/external/provisioner/ExternalPathPayloadProvisioner.java @@ -71,6 +71,7 @@ import com.cloud.agent.api.StartCommand; import com.cloud.agent.api.StopAnswer; import com.cloud.agent.api.StopCommand; import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.ExternalProvisioner; @@ -128,7 +129,7 @@ public class ExternalPathPayloadProvisioner extends ManagerBase implements Exter private ExecutorService payloadCleanupExecutor; private ScheduledExecutorService payloadCleanupScheduler; private static final List TRIVIAL_ACTIONS = Arrays.asList( - "status" + "status", "statuses" ); @Override @@ -456,7 +457,7 @@ public class ExternalPathPayloadProvisioner extends ManagerBase implements Exter @Override public Map getHostVmStateReport(long hostId, String extensionName, String extensionRelativePath) { - final Map vmStates = new HashMap<>(); + Map vmStates = new HashMap<>(); String extensionPath = getExtensionCheckedPath(extensionName, extensionRelativePath); if (StringUtils.isEmpty(extensionPath)) { return vmStates; @@ -466,14 +467,20 @@ public class ExternalPathPayloadProvisioner extends ManagerBase implements Exter logger.error("Host with ID: {} not found", hostId); return vmStates; } + Map> accessDetails = + extensionsManager.getExternalAccessDetails(host, null); + vmStates = getVmPowerStates(host, accessDetails, extensionName, extensionPath); + if (vmStates != null) { + logger.debug("Found {} VMs on the host {}", vmStates.size(), host); + return vmStates; + } + vmStates = new HashMap<>(); List allVms = _uservmDao.listByHostId(hostId); allVms.addAll(_uservmDao.listByLastHostId(hostId)); if (CollectionUtils.isEmpty(allVms)) { logger.debug("No VMs found for the {}", host); return vmStates; } - Map> accessDetails = - extensionsManager.getExternalAccessDetails(host, null); for (UserVmVO vm: allVms) { VirtualMachine.PowerState powerState = getVmPowerState(vm, accessDetails, extensionName, extensionPath); vmStates.put(vm.getInstanceName(), new HostVmStateReportEntry(powerState, "host-" + hostId)); @@ -714,7 +721,7 @@ public class ExternalPathPayloadProvisioner extends ManagerBase implements Exter return getPowerStateFromString(response); } try { - JsonObject jsonObj = new JsonParser().parse(response).getAsJsonObject(); + JsonObject jsonObj = JsonParser.parseString(response).getAsJsonObject(); String powerState = jsonObj.has("power_state") ? jsonObj.get("power_state").getAsString() : null; return getPowerStateFromString(powerState); } catch (Exception e) { @@ -724,7 +731,7 @@ public class ExternalPathPayloadProvisioner extends ManagerBase implements Exter } } - private VirtualMachine.PowerState getVmPowerState(UserVmVO userVmVO, Map> accessDetails, + protected VirtualMachine.PowerState getVmPowerState(UserVmVO userVmVO, Map> accessDetails, String extensionName, String extensionPath) { VirtualMachineTO virtualMachineTO = getVirtualMachineTO(userVmVO); accessDetails.put(ApiConstants.VIRTUAL_MACHINE, virtualMachineTO.getExternalDetails()); @@ -740,6 +747,46 @@ public class ExternalPathPayloadProvisioner extends ManagerBase implements Exter } return parsePowerStateFromResponse(userVmVO, result.second()); } + + protected Map getVmPowerStates(Host host, + Map> accessDetails, String extensionName, String extensionPath) { + Map modifiedDetails = loadAccessDetails(accessDetails, null); + logger.debug("Trying to get VM power statuses from the external system for {}", host); + Pair result = getInstanceStatusesOnExternalSystem(extensionName, extensionPath, + host.getName(), modifiedDetails, AgentManager.Wait.value()); + if (!result.first()) { + logger.warn("Failure response received while trying to fetch the power statuses for {} : {}", + host, result.second()); + return null; + } + if (StringUtils.isBlank(result.second())) { + logger.warn("Empty response while trying to fetch VM power statuses for host: {}", host); + return null; + } + try { + JsonObject jsonObj = JsonParser.parseString(result.second()).getAsJsonObject(); + if (!jsonObj.has("status") || !"success".equalsIgnoreCase(jsonObj.get("status").getAsString())) { + logger.warn("Invalid status in response while trying to fetch VM power statuses for host: {}: {}", + host, result.second()); + return null; + } + if (!jsonObj.has("power_state") || !jsonObj.get("power_state").isJsonObject()) { + logger.warn("Missing or invalid power_state in response for host: {}: {}", host, result.second()); + return null; + } + JsonObject powerStates = jsonObj.getAsJsonObject("power_state"); + Map states = new HashMap<>(); + for (Map.Entry entry : powerStates.entrySet()) { + VirtualMachine.PowerState powerState = getPowerStateFromString(entry.getValue().getAsString()); + states.put(entry.getKey(), new HostVmStateReportEntry(powerState, "host-" + host.getId())); + } + return states; + } catch (Exception e) { + logger.warn("Failed to parse VM power statuses response for host: {}: {}", host, e.getMessage()); + return null; + } + } + public Pair prepareExternalProvisioningInternal(String extensionName, String filename, String vmUUID, Map accessDetails, int wait) { return executeExternalCommand(extensionName, "prepare", accessDetails, wait, @@ -783,6 +830,12 @@ public class ExternalPathPayloadProvisioner extends ManagerBase implements Exter String.format("Failed to get the instance power status %s on external system", vmUUID), filename); } + public Pair getInstanceStatusesOnExternalSystem(String extensionName, String filename, + String hostName, Map accessDetails, int wait) { + return executeExternalCommand(extensionName, "statuses", accessDetails, wait, + String.format("Failed to get the %s instances power status on external system", hostName), filename); + } + public Pair getInstanceConsoleOnExternalSystem(String extensionName, String filename, String vmUUID, Map accessDetails, int wait) { return executeExternalCommand(extensionName, "getconsole", accessDetails, wait, diff --git a/plugins/hypervisors/external/src/test/java/org/apache/cloudstack/hypervisor/external/provisioner/ExternalPathPayloadProvisionerTest.java b/plugins/hypervisors/external/src/test/java/org/apache/cloudstack/hypervisor/external/provisioner/ExternalPathPayloadProvisionerTest.java index d0a396f7a94..e8ab92c986e 100644 --- a/plugins/hypervisors/external/src/test/java/org/apache/cloudstack/hypervisor/external/provisioner/ExternalPathPayloadProvisionerTest.java +++ b/plugins/hypervisors/external/src/test/java/org/apache/cloudstack/hypervisor/external/provisioner/ExternalPathPayloadProvisionerTest.java @@ -79,6 +79,7 @@ import com.cloud.agent.api.StartCommand; import com.cloud.agent.api.StopAnswer; import com.cloud.agent.api.StopCommand; import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor; @@ -761,6 +762,37 @@ public class ExternalPathPayloadProvisionerTest { assertNull(result); } + @Test + public void getVmPowerStatesReturnsValidStatesWhenResponseIsSuccessful() { + Host host = mock(Host.class); + when(host.getId()).thenReturn(1L); + when(host.getName()).thenReturn("test-host"); + + Map> accessDetails = new HashMap<>(); + doReturn(new Pair<>(true, "{\"status\":\"success\",\"power_state\":{\"vm1\":\"PowerOn\",\"vm2\":\"PowerOff\"}}")) + .when(provisioner).getInstanceStatusesOnExternalSystem(anyString(), anyString(), anyString(), anyMap(), anyInt()); + + Map result = provisioner.getVmPowerStates(host, accessDetails, "test-extension", "test-path"); + + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals(VirtualMachine.PowerState.PowerOn, result.get("vm1").getState()); + assertEquals(VirtualMachine.PowerState.PowerOff, result.get("vm2").getState()); + } + + @Test + public void getVmPowerStatesReturnsNullWhenResponseIsFailure() { + Host host = mock(Host.class); + when(host.getName()).thenReturn("test-host"); + + Map> accessDetails = new HashMap<>(); + doReturn(new Pair<>(false, "Error")).when(provisioner) + .getInstanceStatusesOnExternalSystem(anyString(), anyString(), anyString(), anyMap(), anyInt()); + + Map result = provisioner.getVmPowerStates(host, accessDetails, "test-extension", "test-path"); + assertNull(result); + } + @Test public void getVirtualMachineTOReturnsValidTOWhenVmIsNotNull() { VirtualMachine vm = mock(VirtualMachine.class); @@ -986,4 +1018,120 @@ public class ExternalPathPayloadProvisionerTest { String result = provisioner.getExtensionConfigureError("test-extension", null); assertEquals("Extension: test-extension not configured", result); } + + @Test + public void getVmPowerStatesReturnsNullWhenResponseIsEmpty() { + Host host = mock(Host.class); + when(host.getName()).thenReturn("test-host"); + + Map> accessDetails = new HashMap<>(); + doReturn(new Pair<>(true, "")).when(provisioner) + .getInstanceStatusesOnExternalSystem(anyString(), anyString(), anyString(), anyMap(), anyInt()); + + Map result = provisioner.getVmPowerStates(host, accessDetails, "test-extension", "test-path"); + + assertNull(result); + } + + @Test + public void getVmPowerStatesReturnsNullWhenResponseHasInvalidStatus() { + Host host = mock(Host.class); + when(host.getName()).thenReturn("test-host"); + + Map> accessDetails = new HashMap<>(); + doReturn(new Pair<>(true, "{\"status\":\"failure\"}")).when(provisioner) + .getInstanceStatusesOnExternalSystem(anyString(), anyString(), anyString(), anyMap(), anyInt()); + + Map result = provisioner.getVmPowerStates(host, accessDetails, "test-extension", "test-path"); + + assertNull(result); + } + + @Test + public void getVmPowerStatesReturnsNullWhenPowerStateIsMissing() { + Host host = mock(Host.class); + when(host.getName()).thenReturn("test-host"); + + Map> accessDetails = new HashMap<>(); + doReturn(new Pair<>(true, "{\"status\":\"success\"}")).when(provisioner) + .getInstanceStatusesOnExternalSystem(anyString(), anyString(), anyString(), anyMap(), anyInt()); + + Map result = provisioner.getVmPowerStates(host, accessDetails, "test-extension", "test-path"); + + assertNull(result); + } + + @Test + public void getVmPowerStatesReturnsNullWhenResponseIsMalformed() { + Host host = mock(Host.class); + when(host.getName()).thenReturn("test-host"); + + Map> accessDetails = new HashMap<>(); + doReturn(new Pair<>(true, "{status:success")).when(provisioner) + .getInstanceStatusesOnExternalSystem(anyString(), anyString(), anyString(), anyMap(), anyInt()); + + Map result = provisioner.getVmPowerStates(host, accessDetails, "test-extension", "test-path"); + + assertNull(result); + } + + @Test + public void getInstanceStatusesOnExternalSystemReturnsSuccessWhenCommandExecutesSuccessfully() { + doReturn(new Pair<>(true, "success")).when(provisioner) + .executeExternalCommand(eq("test-extension"), eq("statuses"), anyMap(), eq(30), anyString(), eq("test-file")); + + Pair result = provisioner.getInstanceStatusesOnExternalSystem( + "test-extension", "test-file", "test-host", new HashMap<>(), 30); + + assertTrue(result.first()); + assertEquals("success", result.second()); + } + + @Test + public void getInstanceStatusesOnExternalSystemReturnsFailureWhenCommandFails() { + doReturn(new Pair<>(false, "error")).when(provisioner) + .executeExternalCommand(eq("test-extension"), eq("statuses"), anyMap(), eq(30), anyString(), eq("test-file")); + + Pair result = provisioner.getInstanceStatusesOnExternalSystem( + "test-extension", "test-file", "test-host", new HashMap<>(), 30); + + assertFalse(result.first()); + assertEquals("error", result.second()); + } + + @Test + public void getInstanceStatusesOnExternalSystemHandlesEmptyResponse() { + doReturn(new Pair<>(true, "")).when(provisioner) + .executeExternalCommand(eq("test-extension"), eq("statuses"), anyMap(), eq(30), anyString(), eq("test-file")); + + Pair result = provisioner.getInstanceStatusesOnExternalSystem( + "test-extension", "test-file", "test-host", new HashMap<>(), 30); + + assertTrue(result.first()); + assertEquals("", result.second()); + } + + @Test + public void getInstanceStatusesOnExternalSystemHandlesNullResponse() { + doReturn(new Pair<>(true, null)).when(provisioner) + .executeExternalCommand(eq("test-extension"), eq("statuses"), anyMap(), eq(30), anyString(), eq("test-file")); + + Pair result = provisioner.getInstanceStatusesOnExternalSystem( + "test-extension", "test-file", "test-host", new HashMap<>(), 30); + + assertTrue(result.first()); + assertNull(result.second()); + } + + @Test + public void getInstanceStatusesOnExternalSystemHandlesInvalidFilePath() { + doReturn(new Pair<>(false, "File not found")).when(provisioner) + .executeExternalCommand(eq("test-extension"), eq("statuses"), anyMap(), eq(30), anyString(), eq("invalid-file")); + + Pair result = provisioner.getInstanceStatusesOnExternalSystem( + "test-extension", "invalid-file", "test-host", new HashMap<>(), 30); + + assertFalse(result.first()); + assertEquals("File not found", result.second()); + } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 0868e3f4bae..0a9e0d2d98e 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -1971,7 +1971,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv for (final String ifNamePattern : ifNamePatterns) { commonPattern.append("|(").append(ifNamePattern).append(".*)"); } - if(fname.matches(commonPattern.toString())) { + if (fname.matches(commonPattern.toString())) { return true; } return false; @@ -2498,11 +2498,10 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv final Pattern pattern = Pattern.compile("(\\D+)(\\d+)(\\D*)(\\d*)(\\D*)(\\d*)"); final Matcher matcher = pattern.matcher(pif); LOGGER.debug("getting broadcast uri for pif " + pif + " and bridge " + brName); - if(matcher.find()) { + if (matcher.find()) { if (brName.startsWith("brvx")){ return BroadcastDomainType.Vxlan.toUri(matcher.group(2)).toString(); - } - else{ + } else { if (!matcher.group(6).isEmpty()) { return BroadcastDomainType.Vlan.toUri(matcher.group(6)).toString(); } else if (!matcher.group(4).isEmpty()) { @@ -3742,7 +3741,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv } else if (volume.getType() == Volume.Type.DATADISK) { final KVMPhysicalDisk physicalDisk = storagePoolManager.getPhysicalDisk(store.getPoolType(), store.getUuid(), data.getPath()); final KVMStoragePool pool = physicalDisk.getPool(); - if(StoragePoolType.RBD.equals(pool.getType())) { + if (StoragePoolType.RBD.equals(pool.getType())) { final int devId = volume.getDiskSeq().intValue(); final String device = mapRbdDevice(physicalDisk); if (device != null) { @@ -5200,7 +5199,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv } for (int i = 0; i < memoryStats.length; i++) { - if(memoryStats[i].getTag() == UNUSEDMEMORY) { + if (memoryStats[i].getTag() == UNUSEDMEMORY) { freeMemory = memoryStats[i].getValue(); break; } @@ -5772,12 +5771,12 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv return hypervisorType; } - public String mapRbdDevice(final KVMPhysicalDisk disk){ + public String mapRbdDevice(final KVMPhysicalDisk disk) { final KVMStoragePool pool = disk.getPool(); //Check if rbd image is already mapped final String[] splitPoolImage = disk.getPath().split("/"); String device = Script.runSimpleBashScript("rbd showmapped | grep \""+splitPoolImage[0]+"[ ]*"+splitPoolImage[1]+"\" | grep -o \"[^ ]*[ ]*$\""); - if(device == null) { + if (device == null) { //If not mapped, map and return mapped device Script.runSimpleBashScript("rbd map " + disk.getPath() + " --id " + pool.getAuthUserName()); device = Script.runSimpleBashScript("rbd showmapped | grep \""+splitPoolImage[0]+"[ ]*"+splitPoolImage[1]+"\" | grep -o \"[^ ]*[ ]*$\""); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java index ceb371f2cae..030d9747d6c 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java @@ -55,6 +55,9 @@ import javax.xml.xpath.XPathFactory; import com.cloud.agent.api.Command; import com.cloud.hypervisor.kvm.resource.LibvirtXMLParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.cloudstack.agent.directdownload.DirectDownloadAnswer; import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand; import org.apache.cloudstack.direct.download.DirectDownloadHelper; @@ -365,6 +368,16 @@ public class KVMStorageProcessor implements StorageProcessor { newTemplate.setPath(primaryVol.getName()); newTemplate.setSize(primaryVol.getSize()); newTemplate.setFormat(getFormat(primaryPool.getType())); + + if (List.of( + StoragePoolType.RBD, + StoragePoolType.PowerFlex, + StoragePoolType.Linstor, + StoragePoolType.FiberChannel).contains(primaryPool.getType())) { + newTemplate.setFormat(ImageFormat.RAW); + } else { + newTemplate.setFormat(ImageFormat.QCOW2); + } data = newTemplate; } else if (destData.getObjectType() == DataObjectType.VOLUME) { final VolumeObjectTO volumeObjectTO = new VolumeObjectTO(); @@ -752,7 +765,7 @@ public class KVMStorageProcessor implements StorageProcessor { templateContent += "snapshot.name=" + dateFormat.format(date) + System.getProperty("line.separator"); - try(FileOutputStream templFo = new FileOutputStream(templateProp);){ + try (FileOutputStream templFo = new FileOutputStream(templateProp);) { templFo.write(templateContent.getBytes()); templFo.flush(); } catch (final IOException e) { @@ -817,11 +830,9 @@ public class KVMStorageProcessor implements StorageProcessor { if (srcData instanceof VolumeObjectTO) { isVolume = true; - } - else if (srcData instanceof SnapshotObjectTO) { + } else if (srcData instanceof SnapshotObjectTO) { isVolume = false; - } - else { + } else { return new CopyCmdAnswer("unsupported object type"); } @@ -887,8 +898,7 @@ public class KVMStorageProcessor implements StorageProcessor { if (isVolume) { templateContent += "volume.name=" + dateFormat.format(date) + System.getProperty("line.separator"); - } - else { + } else { templateContent += "snapshot.name=" + dateFormat.format(date) + System.getProperty("line.separator"); } @@ -926,8 +936,7 @@ public class KVMStorageProcessor implements StorageProcessor { } catch (Exception ex) { if (isVolume) { logger.debug("Failed to create template from volume: ", ex); - } - else { + } else { logger.debug("Failed to create template from snapshot: ", ex); } @@ -1088,7 +1097,7 @@ public class KVMStorageProcessor implements StorageProcessor { q.convert(srcFile, destFile); final File snapFile = new File(snapshotFile); - if(snapFile.exists()) { + if (snapFile.exists()) { size = snapFile.length(); } @@ -1121,7 +1130,7 @@ public class KVMStorageProcessor implements StorageProcessor { return new CopyCmdAnswer(result); } final File snapFile = new File(snapshotDestPath + "/" + descName); - if(snapFile.exists()){ + if (snapFile.exists()) { size = snapFile.length(); } } @@ -1460,7 +1469,7 @@ public class KVMStorageProcessor implements StorageProcessor { if (resource.getHypervisorType() == Hypervisor.HypervisorType.LXC) { final String device = resource.mapRbdDevice(attachingDisk); if (device != null) { - logger.debug("RBD device on host is: "+device); + logger.debug("RBD device on host is: " + device); attachingDisk.setPath(device); } } @@ -1487,11 +1496,11 @@ public class KVMStorageProcessor implements StorageProcessor { } diskdef.setSerial(serial); if (attachingPool.getType() == StoragePoolType.RBD) { - if(resource.getHypervisorType() == Hypervisor.HypervisorType.LXC){ + if (resource.getHypervisorType() == Hypervisor.HypervisorType.LXC) { // For LXC, map image to host and then attach to Vm final String device = resource.mapRbdDevice(attachingDisk); if (device != null) { - logger.debug("RBD device on host is: "+device); + logger.debug("RBD device on host is: " + device); diskdef.defBlockBasedDisk(device, devId, busT); } else { throw new InternalErrorException("Error while mapping disk "+attachingDisk.getPath()+" on host"); @@ -1561,7 +1570,7 @@ public class KVMStorageProcessor implements StorageProcessor { if ((iopsWriteRateMaxLength != null) && (iopsWriteRateMaxLength > 0)) { diskdef.setIopsWriteRateMaxLength(iopsWriteRateMaxLength); } - if(cacheMode != null) { + if (cacheMode != null) { diskdef.setCacheMode(DiskDef.DiskCacheMode.valueOf(cacheMode.toUpperCase())); } @@ -1748,7 +1757,7 @@ public class KVMStorageProcessor implements StorageProcessor { } final VolumeObjectTO newVol = new VolumeObjectTO(); - if(vol != null) { + if (vol != null) { newVol.setPath(vol.getName()); if (vol.getQemuEncryptFormat() != null) { newVol.setEncryptFormat(vol.getQemuEncryptFormat().toString()); @@ -1853,8 +1862,11 @@ public class KVMStorageProcessor implements StorageProcessor { } } else { if (primaryPool.getType() == StoragePoolType.RBD) { - takeRbdVolumeSnapshotOfStoppedVm(primaryPool, disk, snapshotName); + Long snapshotSize = takeRbdVolumeSnapshotOfStoppedVm(primaryPool, disk, snapshotName); newSnapshot.setPath(snapshotPath); + if (snapshotSize != null) { + newSnapshot.setPhysicalSize(snapshotSize); + } } else if (primaryPool.getType() == StoragePoolType.CLVM) { CreateObjectAnswer result = takeClvmVolumeSnapshotOfStoppedVm(disk, snapshotName); if (result != null) return result; @@ -2306,7 +2318,8 @@ public class KVMStorageProcessor implements StorageProcessor { * barriers properly (>2.6.32) this won't be any different then pulling the power * cord out of a running machine. */ - private void takeRbdVolumeSnapshotOfStoppedVm(KVMStoragePool primaryPool, KVMPhysicalDisk disk, String snapshotName) { + private Long takeRbdVolumeSnapshotOfStoppedVm(KVMStoragePool primaryPool, KVMPhysicalDisk disk, String snapshotName) { + Long snapshotSize = null; try { Rados r = radosConnect(primaryPool); @@ -2317,11 +2330,43 @@ public class KVMStorageProcessor implements StorageProcessor { logger.debug("Attempting to create RBD snapshot {}@{}", disk.getName(), snapshotName); image.snapCreate(snapshotName); + image.snapCreate(snapshotName); + long rbdSnapshotSize = getRbdSnapshotSize(primaryPool.getSourceDir(), disk.getName(), snapshotName, primaryPool.getSourceHost(), primaryPool.getAuthUserName(), primaryPool.getAuthSecret()); + if (rbdSnapshotSize > 0) { + snapshotSize = rbdSnapshotSize; + } + rbd.close(image); r.ioCtxDestroy(io); } catch (final Exception e) { logger.error("A RBD snapshot operation on [{}] failed. The error was: {}", disk.getName(), e.getMessage(), e); } + return snapshotSize; + } + + private long getRbdSnapshotSize(String poolPath, String diskName, String snapshotName, String rbdMonitor, String authUser, String authSecret) { + logger.debug("Get RBD snapshot size for {}/{}@{}", poolPath, diskName, snapshotName); + //cmd: rbd du /@ --format json --mon-host --id --key 2>/dev/null + String snapshotDetailsInJson = Script.runSimpleBashScript(String.format("rbd du %s/%s@%s --format json --mon-host %s --id %s --key %s 2>/dev/null", poolPath, diskName, snapshotName, rbdMonitor, authUser, authSecret)); + if (StringUtils.isNotBlank(snapshotDetailsInJson)) { + ObjectMapper mapper = new ObjectMapper(); + try { + JsonNode root = mapper.readTree(snapshotDetailsInJson); + for (JsonNode image : root.path("images")) { + if (snapshotName.equals(image.path("snapshot").asText())) { + long usedSizeInBytes = image.path("used_size").asLong(); + logger.debug("RBD snapshot {}/{}@{} used size in bytes: {}", poolPath, diskName, snapshotName, usedSizeInBytes); + return usedSizeInBytes; + } + } + } catch (JsonProcessingException e) { + logger.error("Unable to get the RBD snapshot size, RBD snapshot cmd output: {}", snapshotDetailsInJson, e); + } + } else { + logger.warn("Failed to get RBD snapshot size for {}/{}@{} - no output for RBD snapshot cmd", poolPath, diskName, snapshotName); + } + + return 0; } /** diff --git a/scripts/vm/hypervisor/external/provisioner/provisioner.sh b/scripts/vm/hypervisor/external/provisioner/provisioner.sh index f067d892f1f..c92ac36f466 100755 --- a/scripts/vm/hypervisor/external/provisioner/provisioner.sh +++ b/scripts/vm/hypervisor/external/provisioner/provisioner.sh @@ -99,6 +99,14 @@ status() { echo '{"status": "success", "power_state": "poweron"}' } +statuses() { + parse_json "$1" || exit 1 + # This external system can not return an output like the following: + # {"status":"success","power_state":{"i-3-23-VM":"poweroff","i-2-25-VM":"poweron"}} + # CloudStack can fallback to retrieving the power state of the single VM using the "status" action + echo '{"status": "error", "message": "Not supported"}' +} + get_console() { parse_json "$1" || exit 1 local response @@ -145,6 +153,9 @@ case $action in status) status "$parameters" ;; + statuses) + statuses "$parameters" + ;; getconsole) get_console "$parameters" ;; diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 55d3c298db1..655f5acb46e 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -4479,7 +4479,7 @@ public class ApiResponseHelper implements ResponseGenerator { } else if (usageRecord.getUsageType() == UsageTypes.BACKUP) { resourceType = ResourceObjectType.Backup; final StringBuilder builder = new StringBuilder(); - builder.append("Backup usage of size ").append(usageRecord.getUsageDisplay()); + builder.append("Backup usage"); if (vmInstance != null) { resourceId = vmInstance.getId(); usageRecResponse.setResourceName(vmInstance.getInstanceName()); @@ -4492,9 +4492,12 @@ public class ApiResponseHelper implements ResponseGenerator { .append(" (").append(backupOffering.getUuid()).append(", user ad-hoc/scheduled backup allowed: ") .append(backupOffering.isUserDrivenBackupAllowed()).append(")"); } - } + builder.append(" with size ").append(toHumanReadableSize(usageRecord.getSize())); + builder.append(" and with virtual size ").append(toHumanReadableSize(usageRecord.getVirtualSize())); usageRecResponse.setDescription(builder.toString()); + usageRecResponse.setSize(usageRecord.getSize()); + usageRecResponse.setVirtualSize(usageRecord.getVirtualSize()); } else if (usageRecord.getUsageType() == UsageTypes.VM_SNAPSHOT) { resourceType = ResourceObjectType.VMSnapshot; VMSnapshotVO vmSnapshotVO = null; diff --git a/systemvm/debian/etc/apache2/vhost.template b/systemvm/debian/etc/apache2/vhost.template index 7f6a5146099..d1c7a2960ee 100644 --- a/systemvm/debian/etc/apache2/vhost.template +++ b/systemvm/debian/etc/apache2/vhost.template @@ -39,8 +39,8 @@ Allow from 127.0.0.0/255.0.0.0 ::1/128 - # Include HTTP configuration **IF SET** - IncludeOptional /etc/apache2/http.conf + # Include CORS configuration **IF SET** + IncludeOptional /etc/apache2/cors.conf @@ -86,8 +86,8 @@ Allow from 127.0.0.0/255.0.0.0 ::1/128 - # Include HTTPS configuration **IF SET** - IncludeOptional /etc/apache2/https.conf + # Include CORS configuration **IF SET** + IncludeOptional /etc/apache2/cors.conf # SSL Engine Switch: # Enable/Disable SSL for this virtual host. diff --git a/systemvm/debian/opt/cloud/bin/configure.py b/systemvm/debian/opt/cloud/bin/configure.py index c3c91d90c5a..bf48be66694 100755 --- a/systemvm/debian/opt/cloud/bin/configure.py +++ b/systemvm/debian/opt/cloud/bin/configure.py @@ -1233,17 +1233,21 @@ class CsRemoteAccessVpn(CsDataBag): CsHelper.start_if_stopped("ipsec") logging.debug("Remote accessvpn data bag %s", self.dbag) + config_changed = False if not self.config.has_public_network(): interface = self.config.address().get_guest_if_by_network_id() if interface: - self.configure_l2tpIpsec(interface.get_ip(), self.dbag[public_ip]) + config_changed = self.configure_l2tpIpsec(interface.get_ip(), self.dbag[public_ip]) self.remoteaccessvpn_iptables(interface.get_device(), interface.get_ip(), self.dbag[public_ip]) else: - self.configure_l2tpIpsec(public_ip, self.dbag[public_ip]) + config_changed = self.configure_l2tpIpsec(public_ip, self.dbag[public_ip]) self.remoteaccessvpn_iptables(self.dbag[public_ip]['public_interface'], public_ip, self.dbag[public_ip]) CsHelper.execute("ipsec update") - CsHelper.execute("systemctl start xl2tpd") + if config_changed: + CsHelper.execute("systemctl restart xl2tpd") + else: + CsHelper.execute("systemctl start xl2tpd") CsHelper.execute("ipsec rereadsecrets") else: logging.debug("Disabling remote access vpn .....") @@ -1266,21 +1270,23 @@ class CsRemoteAccessVpn(CsDataBag): l2tpfile = CsFile(l2tpconffile) l2tpfile.addeq(" left=%s" % left) l2tpfile.addeq(" leftid=%s" % obj['vpn_server_ip']) - l2tpfile.commit() + l2tp_changed = l2tpfile.commit() secret = CsFile(vpnsecretfilte) secret.empty() secret.addeq(": PSK \"%s\"" % (psk)) - secret.commit() + secret_changed = secret.commit() xl2tpdconf = CsFile(xl2tpdconffile) xl2tpdconf.addeq("ip range = %s" % iprange) xl2tpdconf.addeq("local ip = %s" % localip) - xl2tpdconf.commit() + xl2tpd_changed = xl2tpdconf.commit() xl2tpoptions = CsFile(xl2tpoptionsfile) xl2tpoptions.search("ms-dns ", "ms-dns %s" % localip) - xl2tpoptions.commit() + xl2tpoptions_changed = xl2tpoptions.commit() + + return l2tp_changed or secret_changed or xl2tpd_changed or xl2tpoptions_changed def remoteaccessvpn_iptables(self, publicdev, publicip, obj): localcidr = obj['local_cidr'] diff --git a/systemvm/debian/opt/cloud/bin/setup/common.sh b/systemvm/debian/opt/cloud/bin/setup/common.sh index 85b78ee16ae..ef1576ab588 100755 --- a/systemvm/debian/opt/cloud/bin/setup/common.sh +++ b/systemvm/debian/opt/cloud/bin/setup/common.sh @@ -924,9 +924,6 @@ parse_cmd_line() { privateMtu) export PRIVATEMTU=$VALUE ;; - useHttpsToUpload) - export USEHTTPS=$VALUE - ;; vncport) export VNCPORT=$VALUE ;; diff --git a/systemvm/debian/opt/cloud/bin/setup/secstorage.sh b/systemvm/debian/opt/cloud/bin/setup/secstorage.sh index b8ed7b54311..5baed567f8f 100755 --- a/systemvm/debian/opt/cloud/bin/setup/secstorage.sh +++ b/systemvm/debian/opt/cloud/bin/setup/secstorage.sh @@ -50,33 +50,14 @@ setup_secstorage() { a2enmod proxy_http a2enmod headers - if [ -z $USEHTTPS ] | $USEHTTPS ; then - if [ -f /etc/apache2/http.conf ]; then - rm -rf /etc/apache2/http.conf - fi - cat >/etc/apache2/https.conf </etc/apache2/http.conf </etc/apache2/cors.conf < - + -import { api } from '@/api/index' +import { getAPI } from '@/api' export default { name: 'SearchFilter', @@ -175,6 +175,7 @@ export default { immediate: true, handler (newFilters) { const clonedFilters = newFilters.map(filter => ({ ...filter })) + this.searchFilters = clonedFilters.map(f => ({ ...f })) const promises = [] for (let idx = 0; idx < clonedFilters.length; idx++) { const filter = clonedFilters[idx] @@ -188,9 +189,10 @@ export default { resolve() } else { this.getSearchFilters(filter.key, filter.value).then((value) => { + const displayValue = (value !== undefined && value !== null && value !== '') ? value : filter.value clonedFilters[idx] = { key: filter.key, - value: value, + value: displayValue, isTag: filter.isTag } resolve() @@ -296,7 +298,7 @@ export default { }, getHypervisor (value) { return new Promise((resolve) => { - api('listHypervisors').then(json => { + getAPI('listHypervisors').then(json => { if (json?.listhypervisorsresponse?.hypervisor) { for (const key in json.listhypervisorsresponse.hypervisor) { const hypervisor = json.listhypervisorsresponse.hypervisor[key] @@ -316,7 +318,7 @@ export default { if (!this.$isValidUuid(id)) { return resolve('') } - api(apiName, { listAll: true, id: id }).then(json => { + getAPI(apiName, { listAll: true, id: id }).then(json => { const items = json && json[responseKey1] && json[responseKey1][responseKey2] if (Array.isArray(items) && items.length > 0 && items[0] && items[0][field] !== undefined) { resolve(items[0][field]) @@ -337,7 +339,7 @@ export default { }, getAlertType (type) { return new Promise((resolve) => { - api('listAlertTypes').then(json => { + getAPI('listAlertTypes').then(json => { const alertTypes = {} for (const key in json.listalerttypesresponse.alerttype) { const alerttype = json.listalerttypesresponse.alerttype[key] @@ -351,7 +353,7 @@ export default { }, getAffinityGroupType (type) { return new Promise((resolve) => { - api('listAffinityGroupTypes').then(json => { + getAPI('listAffinityGroupTypes').then(json => { const alertTypes = {} for (const key in json.listaffinitygrouptypesresponse.affinityGroupType) { const affinityGroupType = json.listaffinitygrouptypesresponse.affinityGroupType[key] diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue index f3f8cdfcedf..f4aa842d8f2 100644 --- a/ui/src/views/AutogenView.vue +++ b/ui/src/views/AutogenView.vue @@ -17,7 +17,10 @@