mirror of https://github.com/apache/cloudstack.git
extension: improve host vm power reporting (#11619)
* extension/proxmox: improve host vm power reporting Add `statuses` action in extensions to report VM power states This PR introduces support for retrieving the power state of all VMs on a host directly from an extension using the new `statuses` action. When available, this provides a single aggregated response, reducing the need for multiple calls. If the extension does not implement `statuses`, the server will gracefully fall back to querying individual VMs using the existing `status` action. This helps with updating the host in CloudStack after out-of-band migrations for the VM. Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com> * address review Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com> --------- Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
This commit is contained in:
parent
81f16b6261
commit
c1c1b0e765
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -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<String> TRIVIAL_ACTIONS = Arrays.asList(
|
||||
"status"
|
||||
"status", "statuses"
|
||||
);
|
||||
|
||||
@Override
|
||||
|
|
@ -456,7 +457,7 @@ public class ExternalPathPayloadProvisioner extends ManagerBase implements Exter
|
|||
@Override
|
||||
public Map<String, HostVmStateReportEntry> getHostVmStateReport(long hostId, String extensionName,
|
||||
String extensionRelativePath) {
|
||||
final Map<String, HostVmStateReportEntry> vmStates = new HashMap<>();
|
||||
Map<String, HostVmStateReportEntry> 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<String, Map<String, String>> 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<UserVmVO> allVms = _uservmDao.listByHostId(hostId);
|
||||
allVms.addAll(_uservmDao.listByLastHostId(hostId));
|
||||
if (CollectionUtils.isEmpty(allVms)) {
|
||||
logger.debug("No VMs found for the {}", host);
|
||||
return vmStates;
|
||||
}
|
||||
Map<String, Map<String, String>> 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<String, Map<String, String>> accessDetails,
|
||||
protected VirtualMachine.PowerState getVmPowerState(UserVmVO userVmVO, Map<String, Map<String, String>> 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<String, HostVmStateReportEntry> getVmPowerStates(Host host,
|
||||
Map<String, Map<String, String>> accessDetails, String extensionName, String extensionPath) {
|
||||
Map<String, Object> modifiedDetails = loadAccessDetails(accessDetails, null);
|
||||
logger.debug("Trying to get VM power statuses from the external system for {}", host);
|
||||
Pair<Boolean, String> 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<String, HostVmStateReportEntry> states = new HashMap<>();
|
||||
for (Map.Entry<String, com.google.gson.JsonElement> 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<Boolean, String> prepareExternalProvisioningInternal(String extensionName, String filename,
|
||||
String vmUUID, Map<String, Object> 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<Boolean, String> getInstanceStatusesOnExternalSystem(String extensionName, String filename,
|
||||
String hostName, Map<String, Object> 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<Boolean, String> getInstanceConsoleOnExternalSystem(String extensionName, String filename,
|
||||
String vmUUID, Map<String, Object> accessDetails, int wait) {
|
||||
return executeExternalCommand(extensionName, "getconsole", accessDetails, wait,
|
||||
|
|
|
|||
|
|
@ -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<String, Map<String, String>> accessDetails = new HashMap<>();
|
||||
doReturn(new Pair<>(true, "{\"status\":\"success\",\"power_state\":{\"vm1\":\"PowerOn\",\"vm2\":\"PowerOff\"}}"))
|
||||
.when(provisioner).getInstanceStatusesOnExternalSystem(anyString(), anyString(), anyString(), anyMap(), anyInt());
|
||||
|
||||
Map<String, HostVmStateReportEntry> 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<String, Map<String, String>> accessDetails = new HashMap<>();
|
||||
doReturn(new Pair<>(false, "Error")).when(provisioner)
|
||||
.getInstanceStatusesOnExternalSystem(anyString(), anyString(), anyString(), anyMap(), anyInt());
|
||||
|
||||
Map<String, HostVmStateReportEntry> 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<String, Map<String, String>> accessDetails = new HashMap<>();
|
||||
doReturn(new Pair<>(true, "")).when(provisioner)
|
||||
.getInstanceStatusesOnExternalSystem(anyString(), anyString(), anyString(), anyMap(), anyInt());
|
||||
|
||||
Map<String, HostVmStateReportEntry> 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<String, Map<String, String>> accessDetails = new HashMap<>();
|
||||
doReturn(new Pair<>(true, "{\"status\":\"failure\"}")).when(provisioner)
|
||||
.getInstanceStatusesOnExternalSystem(anyString(), anyString(), anyString(), anyMap(), anyInt());
|
||||
|
||||
Map<String, HostVmStateReportEntry> 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<String, Map<String, String>> accessDetails = new HashMap<>();
|
||||
doReturn(new Pair<>(true, "{\"status\":\"success\"}")).when(provisioner)
|
||||
.getInstanceStatusesOnExternalSystem(anyString(), anyString(), anyString(), anyMap(), anyInt());
|
||||
|
||||
Map<String, HostVmStateReportEntry> 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<String, Map<String, String>> accessDetails = new HashMap<>();
|
||||
doReturn(new Pair<>(true, "{status:success")).when(provisioner)
|
||||
.getInstanceStatusesOnExternalSystem(anyString(), anyString(), anyString(), anyMap(), anyInt());
|
||||
|
||||
Map<String, HostVmStateReportEntry> 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<Boolean, String> 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<Boolean, String> 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<Boolean, String> 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<Boolean, String> 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<Boolean, String> result = provisioner.getInstanceStatusesOnExternalSystem(
|
||||
"test-extension", "invalid-file", "test-host", new HashMap<>(), 30);
|
||||
|
||||
assertFalse(result.first());
|
||||
assertEquals("File not found", result.second());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
;;
|
||||
|
|
|
|||
Loading…
Reference in New Issue