mirror of https://github.com/apache/cloudstack.git
Merge branch '4.22'
This commit is contained in:
commit
a5b6bc3be6
|
|
@ -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},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,9 +48,7 @@ public class SnapshotObjectTO extends DownloadableObjectTO implements DataTO {
|
|||
private Long physicalSize = (long) 0;
|
||||
private long accountId;
|
||||
|
||||
|
||||
public SnapshotObjectTO() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 \"[^ ]*[ ]*$\"");
|
||||
|
|
|
|||
|
|
@ -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 <pool>/<disk-name>@<snapshot-name> --format json --mon-host <monitor-host> --id <user> --key <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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -39,8 +39,8 @@
|
|||
Allow from 127.0.0.0/255.0.0.0 ::1/128
|
||||
</Directory>
|
||||
|
||||
# Include HTTP configuration **IF SET**
|
||||
IncludeOptional /etc/apache2/http.conf
|
||||
# Include CORS configuration **IF SET**
|
||||
IncludeOptional /etc/apache2/cors.conf
|
||||
|
||||
</VirtualHost>
|
||||
|
||||
|
|
@ -86,8 +86,8 @@
|
|||
Allow from 127.0.0.0/255.0.0.0 ::1/128
|
||||
</Directory>
|
||||
|
||||
# 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.
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
|
|||
|
|
@ -924,9 +924,6 @@ parse_cmd_line() {
|
|||
privateMtu)
|
||||
export PRIVATEMTU=$VALUE
|
||||
;;
|
||||
useHttpsToUpload)
|
||||
export USEHTTPS=$VALUE
|
||||
;;
|
||||
vncport)
|
||||
export VNCPORT=$VALUE
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -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 <<HTTPS
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTPS} =on
|
||||
RewriteCond %{REQUEST_METHOD} =POST
|
||||
RewriteRule ^/upload/(.*) http://127.0.0.1:8210/upload?uuid=\$1 [P,L]
|
||||
Header always set Access-Control-Allow-Origin "*"
|
||||
Header always set Access-Control-Allow-Methods "POST, OPTIONS"
|
||||
Header always set Access-Control-Allow-Headers "x-requested-with, content-type, origin, authorization, accept, client-security-token, x-signature, x-metadata, x-expires"
|
||||
HTTPS
|
||||
else
|
||||
if [ -f /etc/apache2/https.conf ]; then
|
||||
rm -rf /etc/apache2/https.conf
|
||||
fi
|
||||
cat >/etc/apache2/http.conf <<HTTP
|
||||
cat >/etc/apache2/cors.conf <<CORS
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_METHOD} =POST
|
||||
RewriteRule ^/upload/(.*) http://127.0.0.1:8210/upload?uuid=\$1 [P,L]
|
||||
Header always set Access-Control-Allow-Origin "*"
|
||||
Header always set Access-Control-Allow-Methods "POST, OPTIONS"
|
||||
Header always set Access-Control-Allow-Headers "x-requested-with, content-type, origin, authorization, accept, client-security-token, x-signature, x-metadata, x-expires"
|
||||
HTTP
|
||||
fi
|
||||
|
||||
CORS
|
||||
|
||||
disable_rpfilter
|
||||
enable_fwding 0
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// 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
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// 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
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
v-for="filter in this.searchFilters"
|
||||
:key="filter.key + filter.value"
|
||||
>
|
||||
<a-col v-if="!['page', 'pagesize', 'q', 'keyword', 'tags'].includes(filter.key)">
|
||||
<a-col v-if="!['page', 'pagesize', 'q', 'keyword', 'tags', 'projectid'].includes(filter.key)">
|
||||
<a-tag
|
||||
v-if="!filter.isTag"
|
||||
closable
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
|
||||
<script>
|
||||
|
||||
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]
|
||||
|
|
|
|||
|
|
@ -17,7 +17,10 @@
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<a-affix :offsetTop="this.$store.getters.maintenanceInitiated || this.$store.getters.shutdownTriggered ? 103 : 78">
|
||||
<a-affix
|
||||
:key="'affix-' + showSearchFilters"
|
||||
:offsetTop="this.$store.getters.maintenanceInitiated || this.$store.getters.shutdownTriggered ? 103 : 78"
|
||||
>
|
||||
<a-card
|
||||
class="breadcrumb-card"
|
||||
style="z-index: 10"
|
||||
|
|
@ -127,11 +130,11 @@
|
|||
</a-col>
|
||||
</a-row>
|
||||
<a-row
|
||||
v-if="!dataView && $config.showSearchFilters"
|
||||
style="min-height: 36px; padding-top: 12px;"
|
||||
v-if="showSearchFilters"
|
||||
style="min-height: 36px; padding-top: 12px; padding-left: 12px;"
|
||||
>
|
||||
<search-filter
|
||||
:filters="getActiveFilters()"
|
||||
:filters="activeFiltersList"
|
||||
:apiName="apiName"
|
||||
@removeFilter="removeFilter"
|
||||
/>
|
||||
|
|
@ -829,6 +832,37 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
activeFiltersList () {
|
||||
const queryParams = Object.assign({}, this.$route.query)
|
||||
const activeFilters = []
|
||||
for (const filter in queryParams) {
|
||||
if (this.$route.name === 'host' && filter === 'type') {
|
||||
continue
|
||||
}
|
||||
if (!filter.startsWith('tags[')) {
|
||||
activeFilters.push({
|
||||
key: filter,
|
||||
value: queryParams[filter],
|
||||
isTag: false
|
||||
})
|
||||
} else if (filter.endsWith('].key')) {
|
||||
const tagIdx = filter.split('[')[1].split(']')[0]
|
||||
const tagKey = queryParams[`tags[${tagIdx}].key`]
|
||||
const tagValue = queryParams[`tags[${tagIdx}].value`]
|
||||
activeFilters.push({
|
||||
key: tagKey,
|
||||
value: tagValue,
|
||||
isTag: true,
|
||||
tagIdx: tagIdx
|
||||
})
|
||||
}
|
||||
}
|
||||
return activeFilters
|
||||
},
|
||||
showSearchFilters () {
|
||||
const excludedKeys = ['page', 'pagesize', 'q', 'keyword', 'tags', 'projectid']
|
||||
return !this.dataView && this.$config.showSearchFilters && this.activeFiltersList.some(f => !excludedKeys.includes(f.key))
|
||||
},
|
||||
hasSelected () {
|
||||
return this.selectedRowKeys.length > 0
|
||||
},
|
||||
|
|
@ -1274,30 +1308,6 @@ export default {
|
|||
eventBus.emit('action-closing', { action: this.currentAction })
|
||||
this.closeAction()
|
||||
},
|
||||
getActiveFilters () {
|
||||
const queryParams = Object.assign({}, this.$route.query)
|
||||
const activeFilters = []
|
||||
for (const filter in queryParams) {
|
||||
if (!filter.startsWith('tags[')) {
|
||||
activeFilters.push({
|
||||
key: filter,
|
||||
value: queryParams[filter],
|
||||
isTag: false
|
||||
})
|
||||
} else if (filter.endsWith('].key')) {
|
||||
const tagIdx = filter.split('[')[1].split(']')[0]
|
||||
const tagKey = queryParams[`tags[${tagIdx}].key`]
|
||||
const tagValue = queryParams[`tags[${tagIdx}].value`]
|
||||
activeFilters.push({
|
||||
key: tagKey,
|
||||
value: tagValue,
|
||||
isTag: true,
|
||||
tagIdx: tagIdx
|
||||
})
|
||||
}
|
||||
}
|
||||
return activeFilters
|
||||
},
|
||||
removeFilter (filter) {
|
||||
const queryParams = Object.assign({}, this.$route.query)
|
||||
if (filter.isTag) {
|
||||
|
|
|
|||
|
|
@ -70,12 +70,11 @@ public class BackupUsageParser extends UsageParser {
|
|||
DecimalFormat dFormat = new DecimalFormat("#.######");
|
||||
String usageDisplay = dFormat.format(usage);
|
||||
|
||||
final Double rawUsage = (double) usageBackup.getSize();
|
||||
final String description = String.format("Backup usage VM ID: %d, backup offering: %d", vmId, offeringId);
|
||||
|
||||
final UsageVO usageRecord =
|
||||
new UsageVO(zoneId, account.getAccountId(), account.getDomainId(), description, usageDisplay + " Hrs",
|
||||
UsageTypes.BACKUP, new Double(usage), vmId, null, offeringId, null, vmId,
|
||||
UsageTypes.BACKUP, (double) usage, vmId, null, offeringId, null, vmId,
|
||||
usageBackup.getSize(), usageBackup.getProtectedSize(), startDate, endDate);
|
||||
usageDao.persist(usageRecord);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ public class VMSnapshotOnPrimaryParser extends UsageParser {
|
|||
String usageDesc = "VMSnapshot Id: " + vmSnapshotId + " On Primary Usage: VM Id: " + vmId;
|
||||
usageDesc += " Size: " + toHumanReadableSize(virtualSize);
|
||||
|
||||
UsageVO usageRecord = new UsageVO(zoneId, account.getId(), account.getDomainId(), usageDesc, usageDisplay + " Hrs", usageType, new Double(usage), vmId, name, null, null,
|
||||
UsageVO usageRecord = new UsageVO(zoneId, account.getId(), account.getDomainId(), usageDesc, usageDisplay + " Hrs", usageType, (double) usage, vmId, name, null, null,
|
||||
vmSnapshotId, physicalSize, virtualSize, startDate, endDate);
|
||||
usageDao.persist(usageRecord);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,4 +142,19 @@ public class DigestHelper {
|
|||
throw new CloudRuntimeException(errMsg, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String prependAlgorithm(String checksum) {
|
||||
if (StringUtils.isEmpty(checksum)) {
|
||||
return checksum;
|
||||
}
|
||||
int checksumLength = checksum.length();
|
||||
Map<String, Integer> paddingLengths = getChecksumLengthsMap();
|
||||
for (Map.Entry<String, Integer> entry : paddingLengths.entrySet()) {
|
||||
if (entry.getValue().equals(checksumLength)) {
|
||||
String algorithm = entry.getKey();
|
||||
return String.format("{%s}%s", algorithm, checksum);
|
||||
}
|
||||
}
|
||||
return checksum;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue