Skip to content

Commit a5b6bc3

Browse files
Merge branch '4.22'
2 parents 7b94ccc + 30d3066 commit a5b6bc3

File tree

22 files changed

+519
-184
lines changed

22 files changed

+519
-184
lines changed

api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.Objects;
2020
import java.util.stream.Stream;
2121

22+
import com.cloud.utils.exception.CloudRuntimeException;
2223
import org.apache.cloudstack.api.ACL;
2324
import org.apache.cloudstack.api.APICommand;
2425
import org.apache.cloudstack.api.ApiConstants;
@@ -39,7 +40,6 @@
3940
import com.cloud.exception.ResourceAllocationException;
4041
import com.cloud.exception.ResourceUnavailableException;
4142
import com.cloud.uservm.UserVm;
42-
import com.cloud.utils.exception.CloudRuntimeException;
4343
import com.cloud.vm.VirtualMachine;
4444

4545
@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},

api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.cloud.exception.ResourceUnavailableException;
2222
import com.cloud.user.Account;
2323
import com.cloud.uservm.UserVm;
24+
import com.cloud.utils.StringUtils;
2425
import com.cloud.utils.exception.CloudRuntimeException;
2526
import com.cloud.utils.net.Dhcp;
2627
import com.cloud.vm.VirtualMachine;
@@ -44,7 +45,6 @@
4445
import org.apache.cloudstack.context.CallContext;
4546
import org.apache.cloudstack.vm.lease.VMLeaseManager;
4647
import org.apache.commons.lang3.EnumUtils;
47-
import org.apache.commons.lang3.StringUtils;
4848

4949
import java.util.Collection;
5050
import java.util.HashMap;
@@ -174,6 +174,9 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction,
174174
/////////////////////////////////////////////////////
175175

176176
public String getDisplayName() {
177+
if (StringUtils.isBlank(displayName)) {
178+
displayName = name;
179+
}
177180
return displayName;
178181
}
179182

core/src/main/java/org/apache/cloudstack/storage/to/SnapshotObjectTO.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,7 @@ public class SnapshotObjectTO extends DownloadableObjectTO implements DataTO {
4848
private Long physicalSize = (long) 0;
4949
private long accountId;
5050

51-
5251
public SnapshotObjectTO() {
53-
5452
}
5553

5654
@Override

engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@ private VMTemplateVO createTemplateObjectInDB(SystemVMTemplateDetails details) {
606606
template.setBits(64);
607607
template.setAccountId(Account.ACCOUNT_ID_SYSTEM);
608608
template.setUrl(details.getUrl());
609-
template.setChecksum(details.getChecksum());
609+
template.setChecksum(DigestHelper.prependAlgorithm(details.getChecksum()));
610610
template.setEnablePassword(false);
611611
template.setDisplayText(details.getName());
612612
template.setFormat(details.getFormat());
@@ -1079,7 +1079,7 @@ protected void updateRegisteredTemplateDetails(Long templateId, MetadataTemplate
10791079
protected void updateTemplateUrlChecksumAndGuestOsId(VMTemplateVO templateVO,
10801080
MetadataTemplateDetails templateDetails) {
10811081
templateVO.setUrl(templateDetails.getUrl());
1082-
templateVO.setChecksum(templateDetails.getChecksum());
1082+
templateVO.setChecksum(DigestHelper.prependAlgorithm(templateDetails.getChecksum()));
10831083
GuestOSVO guestOS = guestOSDao.findOneByDisplayName(templateDetails.getGuestOs());
10841084
if (guestOS != null) {
10851085
templateVO.setGuestOSId(guestOS.getId());

engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotObject.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,13 +398,16 @@ public void processEvent(ObjectInDataStoreStateMachine.Event event, Answer answe
398398
if (answer instanceof CreateObjectAnswer) {
399399
SnapshotObjectTO snapshotTO = (SnapshotObjectTO)((CreateObjectAnswer)answer).getData();
400400
snapshotStore.setInstallPath(snapshotTO.getPath());
401+
if (snapshotTO.getPhysicalSize() != null && snapshotTO.getPhysicalSize() > 0L) {
402+
snapshotStore.setPhysicalSize(snapshotTO.getPhysicalSize());
403+
}
401404
snapshotStoreDao.update(snapshotStore.getId(), snapshotStore);
402405
} else if (answer instanceof CopyCmdAnswer) {
403406
SnapshotObjectTO snapshotTO = (SnapshotObjectTO)((CopyCmdAnswer)answer).getNewData();
404407
snapshotStore.setInstallPath(snapshotTO.getPath());
405408
if (snapshotTO.getPhysicalSize() != null) {
406409
// For S3 delta snapshot, physical size is currently not set
407-
snapshotStore.setPhysicalSize(snapshotTO.getPhysicalSize());
410+
snapshotStore.setPhysicalSize(snapshotTO.getPhysicalSize());
408411
}
409412
if (snapshotTO.getParentSnapshotPath() == null) {
410413
snapshotStore.setParentSnapshotId(0L);

extensions/HyperV/hyperv.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,29 @@ def status(self):
210210
power_state = "poweroff"
211211
succeed({"status": "success", "power_state": power_state})
212212

213+
def statuses(self):
214+
command = 'Get-VM | Select-Object Name, State | ConvertTo-Json'
215+
output = self.run_ps(command)
216+
if not output or output.strip() in ("", "null"):
217+
vms = []
218+
else:
219+
try:
220+
vms = json.loads(output)
221+
except json.JSONDecodeError:
222+
fail("Failed to parse VM status output: " + output)
223+
power_state = {}
224+
if isinstance(vms, dict):
225+
vms = [vms]
226+
for vm in vms:
227+
state = vm["State"].strip().lower()
228+
if state == "running":
229+
power_state[vm["Name"]] = "poweron"
230+
elif state == "off":
231+
power_state[vm["Name"]] = "poweroff"
232+
else:
233+
power_state[vm["Name"]] = "unknown"
234+
succeed({"status": "success", "power_state": power_state})
235+
213236
def delete(self):
214237
try:
215238
self.run_ps_int(f'Remove-VM -Name "{self.data["vmname"]}" -Force')
@@ -286,6 +309,7 @@ def main():
286309
"reboot": manager.reboot,
287310
"delete": manager.delete,
288311
"status": manager.status,
312+
"statuses": manager.statuses,
289313
"getconsole": manager.get_console,
290314
"suspend": manager.suspend,
291315
"resume": manager.resume,

extensions/Proxmox/proxmox.sh

Lines changed: 103 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ parse_json() {
6464
token="${host_token:-$extension_token}"
6565
secret="${host_secret:-$extension_secret}"
6666

67-
check_required_fields vm_internal_name url user token secret node
67+
check_required_fields url user token secret node
6868
}
6969

7070
urlencode() {
@@ -206,6 +206,10 @@ prepare() {
206206

207207
create() {
208208
if [[ -z "$vm_name" ]]; then
209+
if [[ -z "$vm_internal_name" ]]; then
210+
echo '{"error":"Missing required fields: vm_internal_name"}'
211+
exit 1
212+
fi
209213
vm_name="$vm_internal_name"
210214
fi
211215
validate_name "VM" "$vm_name"
@@ -331,71 +335,102 @@ get_node_host() {
331335
echo "$host"
332336
}
333337

334-
get_console() {
335-
check_required_fields node vmid
336-
337-
local api_resp port ticket
338-
if ! api_resp="$(call_proxmox_api POST "/nodes/${node}/qemu/${vmid}/vncproxy")"; then
339-
echo "$api_resp" | jq -c '{status:"error", error:(.errors.curl // (.errors|tostring))}'
340-
exit 1
341-
fi
342-
343-
port="$(echo "$api_resp" | jq -re '.data.port // empty' 2>/dev/null || true)"
344-
ticket="$(echo "$api_resp" | jq -re '.data.ticket // empty' 2>/dev/null || true)"
345-
346-
if [[ -z "$port" || -z "$ticket" ]]; then
347-
jq -n --arg raw "$api_resp" \
348-
'{status:"error", error:"Proxmox response missing port/ticket", upstream:$raw}'
349-
exit 1
350-
fi
351-
352-
# Derive host from node’s network info
353-
local host
354-
host="$(get_node_host)"
355-
if [[ -z "$host" ]]; then
356-
jq -n --arg msg "Could not determine host IP for node $node" \
357-
'{status:"error", error:$msg}'
358-
exit 1
359-
fi
360-
361-
jq -n \
362-
--arg host "$host" \
363-
--arg port "$port" \
364-
--arg password "$ticket" \
365-
--argjson passwordonetimeuseonly true \
366-
'{
367-
status: "success",
368-
message: "Console retrieved",
369-
console: {
370-
host: $host,
371-
port: $port,
372-
password: $password,
373-
passwordonetimeuseonly: $passwordonetimeuseonly,
374-
protocol: "vnc"
375-
}
376-
}'
377-
}
338+
get_console() {
339+
check_required_fields node vmid
378340

379-
list_snapshots() {
380-
snapshot_response=$(call_proxmox_api GET "/nodes/${node}/qemu/${vmid}/snapshot")
381-
echo "$snapshot_response" | jq '
382-
def to_date:
383-
if . == "-" then "-"
384-
elif . == null then "-"
385-
else (. | tonumber | strftime("%Y-%m-%d %H:%M:%S"))
386-
end;
341+
local api_resp port ticket
342+
if ! api_resp="$(call_proxmox_api POST "/nodes/${node}/qemu/${vmid}/vncproxy")"; then
343+
echo "$api_resp" | jq -c '{status:"error", error:(.errors.curl // (.errors|tostring))}'
344+
exit 1
345+
fi
346+
347+
port="$(echo "$api_resp" | jq -re '.data.port // empty' 2>/dev/null || true)"
348+
ticket="$(echo "$api_resp" | jq -re '.data.ticket // empty' 2>/dev/null || true)"
349+
350+
if [[ -z "$port" || -z "$ticket" ]]; then
351+
jq -n --arg raw "$api_resp" \
352+
'{status:"error", error:"Proxmox response missing port/ticket", upstream:$raw}'
353+
exit 1
354+
fi
355+
356+
# Derive host from node’s network info
357+
local host
358+
host="$(get_node_host)"
359+
if [[ -z "$host" ]]; then
360+
jq -n --arg msg "Could not determine host IP for node $node" \
361+
'{status:"error", error:$msg}'
362+
exit 1
363+
fi
364+
365+
jq -n \
366+
--arg host "$host" \
367+
--arg port "$port" \
368+
--arg password "$ticket" \
369+
--argjson passwordonetimeuseonly true \
370+
'{
371+
status: "success",
372+
message: "Console retrieved",
373+
console: {
374+
host: $host,
375+
port: $port,
376+
password: $password,
377+
passwordonetimeuseonly: $passwordonetimeuseonly,
378+
protocol: "vnc"
379+
}
380+
}'
381+
}
382+
383+
statuses() {
384+
local response
385+
response=$(call_proxmox_api GET "/nodes/${node}/qemu")
386+
387+
if [[ -z "$response" ]]; then
388+
echo '{"status":"error","message":"empty response from Proxmox API"}'
389+
return 1
390+
fi
391+
392+
if ! echo "$response" | jq empty >/dev/null 2>&1; then
393+
echo '{"status":"error","message":"invalid JSON response from Proxmox API"}'
394+
return 1
395+
fi
396+
397+
echo "$response" | jq -c '
398+
def map_state(s):
399+
if s=="running" then "poweron"
400+
elif s=="stopped" then "poweroff"
401+
else "unknown" end;
387402
388403
{
389404
status: "success",
390-
printmessage: "true",
391-
message: [.data[] | {
392-
name: .name,
393-
snaptime: ((.snaptime // "-") | to_date),
394-
description: .description,
395-
parent: (.parent // "-"),
396-
vmstate: (.vmstate // "-")
397-
}]
398-
}
405+
power_state: (
406+
.data
407+
| map(select(.template != 1))
408+
| map({ ( (.name // (.vmid|tostring)) ): map_state(.status) })
409+
| add // {}
410+
)
411+
}'
412+
}
413+
414+
list_snapshots() {
415+
snapshot_response=$(call_proxmox_api GET "/nodes/${node}/qemu/${vmid}/snapshot")
416+
echo "$snapshot_response" | jq '
417+
def to_date:
418+
if . == "-" then "-"
419+
elif . == null then "-"
420+
else (. | tonumber | strftime("%Y-%m-%d %H:%M:%S"))
421+
end;
422+
423+
{
424+
status: "success",
425+
printmessage: "true",
426+
message: [.data[] | {
427+
name: .name,
428+
snaptime: ((.snaptime // "-") | to_date),
429+
description: .description,
430+
parent: (.parent // "-"),
431+
vmstate: (.vmstate // "-")
432+
}]
433+
}
399434
'
400435
}
401436

@@ -463,9 +498,9 @@ parse_json "$parameters" || exit 1
463498

464499
cleanup_vm=0
465500
cleanup() {
466-
if (( cleanup_vm == 1 )); then
467-
execute_and_wait DELETE "/nodes/${node}/qemu/${vmid}"
468-
fi
501+
if (( cleanup_vm == 1 )); then
502+
execute_and_wait DELETE "/nodes/${node}/qemu/${vmid}"
503+
fi
469504
}
470505

471506
trap cleanup EXIT
@@ -492,6 +527,9 @@ case $action in
492527
status)
493528
status
494529
;;
530+
statuses)
531+
statuses
532+
;;
495533
getconsole)
496534
get_console
497535
;;

0 commit comments

Comments
 (0)