Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class ApiConstants {
public static final String ALGORITHM = "algorithm";
public static final String ALIAS = "alias";
public static final String ALLOCATED_ONLY = "allocatedonly";
public static final String ALLOW_USER_FORCE_STOP_VM = "allowuserforcestopvm";
public static final String ANNOTATION = "annotation";
public static final String API_KEY = "apikey";
public static final String ARCHIVED = "archived";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public void execute() {
response.setAllowUserExpungeRecoverVM((Boolean)capabilities.get("allowUserExpungeRecoverVM"));
response.setAllowUserExpungeRecoverVolume((Boolean)capabilities.get("allowUserExpungeRecoverVolume"));
response.setAllowUserViewAllDomainAccounts((Boolean)capabilities.get("allowUserViewAllDomainAccounts"));
response.setAllowUserForceStopVM((Boolean)capabilities.get(ApiConstants.ALLOW_USER_FORCE_STOP_VM));
response.setKubernetesServiceEnabled((Boolean)capabilities.get("kubernetesServiceEnabled"));
response.setKubernetesClusterExperimentalFeaturesEnabled((Boolean)capabilities.get("kubernetesClusterExperimentalFeaturesEnabled"));
response.setCustomHypervisorDisplayName((String) capabilities.get("customHypervisorDisplayName"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ public class CapabilitiesResponse extends BaseResponse {
@Param(description = "true if users can see all accounts within the same domain, false otherwise")
private boolean allowUserViewAllDomainAccounts;

@SerializedName(ApiConstants.ALLOW_USER_FORCE_STOP_VM)
@Param(description = "true if users are allowed to force stop a vm, false otherwise", since = "4.20.0")
private boolean allowUserForceStopVM;

@SerializedName("kubernetesserviceenabled")
@Param(description = "true if Kubernetes Service plugin is enabled, false otherwise")
private boolean kubernetesServiceEnabled;
Expand Down Expand Up @@ -192,6 +196,10 @@ public void setAllowUserViewAllDomainAccounts(boolean allowUserViewAllDomainAcco
this.allowUserViewAllDomainAccounts = allowUserViewAllDomainAccounts;
}

public void setAllowUserForceStopVM(boolean allowUserForceStopVM) {
this.allowUserForceStopVM = allowUserForceStopVM;
}

public void setKubernetesServiceEnabled(boolean kubernetesServiceEnabled) {
this.kubernetesServiceEnabled = kubernetesServiceEnabled;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4459,6 +4459,7 @@ public Map<String, Object> listCapabilities(final ListCapabilitiesCmd cmd) {
final boolean allowUserViewDestroyedVM = (QueryService.AllowUserViewDestroyedVM.valueIn(caller.getId()) | _accountService.isAdmin(caller.getId()));
final boolean allowUserExpungeRecoverVM = (UserVmManager.AllowUserExpungeRecoverVm.valueIn(caller.getId()) | _accountService.isAdmin(caller.getId()));
final boolean allowUserExpungeRecoverVolume = (VolumeApiServiceImpl.AllowUserExpungeRecoverVolume.valueIn(caller.getId()) | _accountService.isAdmin(caller.getId()));
final boolean allowUserForceStopVM = (UserVmManager.AllowUserForceStopVm.valueIn(caller.getId()) | _accountService.isAdmin(caller.getId()));

final boolean allowUserViewAllDomainAccounts = (QueryService.AllowUserViewAllDomainAccounts.valueIn(caller.getDomainId()));

Expand Down Expand Up @@ -4486,6 +4487,7 @@ public Map<String, Object> listCapabilities(final ListCapabilitiesCmd cmd) {
capabilities.put("allowUserExpungeRecoverVM", allowUserExpungeRecoverVM);
capabilities.put("allowUserExpungeRecoverVolume", allowUserExpungeRecoverVolume);
capabilities.put("allowUserViewAllDomainAccounts", allowUserViewAllDomainAccounts);
capabilities.put(ApiConstants.ALLOW_USER_FORCE_STOP_VM, allowUserForceStopVM);
capabilities.put("kubernetesServiceEnabled", kubernetesServiceEnabled);
capabilities.put("kubernetesClusterExperimentalFeaturesEnabled", kubernetesClusterExperimentalFeaturesEnabled);
capabilities.put("customHypervisorDisplayName", HypervisorGuru.HypervisorCustomDisplayName.value());
Expand Down
3 changes: 3 additions & 0 deletions server/src/main/java/com/cloud/vm/UserVmManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,15 @@ public interface UserVmManager extends UserVmService {
String EnableDynamicallyScaleVmCK = "enable.dynamic.scale.vm";
String AllowDiskOfferingChangeDuringScaleVmCK = "allow.diskoffering.change.during.scale.vm";
String AllowUserExpungeRecoverVmCK ="allow.user.expunge.recover.vm";
String AllowUserForceStopVmCK = "allow.user.force.stop.vm";
ConfigKey<Boolean> EnableDynamicallyScaleVm = new ConfigKey<Boolean>("Advanced", Boolean.class, EnableDynamicallyScaleVmCK, "false",
"Enables/Disables dynamically scaling a vm", true, ConfigKey.Scope.Zone);
ConfigKey<Boolean> AllowDiskOfferingChangeDuringScaleVm = new ConfigKey<Boolean>("Advanced", Boolean.class, AllowDiskOfferingChangeDuringScaleVmCK, "false",
"Determines whether to allow or disallow disk offering change for root volume during scaling of a stopped or running vm", true, ConfigKey.Scope.Zone);
ConfigKey<Boolean> AllowUserExpungeRecoverVm = new ConfigKey<Boolean>("Advanced", Boolean.class, AllowUserExpungeRecoverVmCK, "false",
"Determines whether users can expunge or recover their vm", true, ConfigKey.Scope.Account);
ConfigKey<Boolean> AllowUserForceStopVm = new ConfigKey<Boolean>("Advanced", Boolean.class, AllowUserForceStopVmCK, "true",
"Determines whether users are allowed to force stop a vm", true, ConfigKey.Scope.Account);
ConfigKey<Boolean> DisplayVMOVFProperties = new ConfigKey<Boolean>("Advanced", Boolean.class, "vm.display.ovf.properties", "false",
"Set display of VMs OVF properties as part of VM details", true);

Expand Down
13 changes: 12 additions & 1 deletion server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -5341,6 +5341,13 @@ protected void updateVncPasswordIfItHasChanged(String originalVncPassword, Strin
public void finalizeExpunge(VirtualMachine vm) {
}

private void checkForceStopVmPermission(Account callingAccount) {
if (!AllowUserForceStopVm.valueIn(callingAccount.getId())) {
logger.error("Parameter [{}] can only be passed by Admin accounts or when the allow.user.force.stop.vm config is true for the account.", ApiConstants.FORCED);
throw new PermissionDeniedException("Account does not have the permission to force stop the vm.");
}
}

@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_STOP, eventDescription = "stopping Vm", async = true)
public UserVm stopVirtualMachine(long vmId, boolean forced) throws ConcurrentOperationException {
Expand All @@ -5358,6 +5365,10 @@ public UserVm stopVirtualMachine(long vmId, boolean forced) throws ConcurrentOpe
throw new InvalidParameterValueException("unable to find a virtual machine with id " + vmId);
}

if (forced) {
checkForceStopVmPermission(caller);
}

// check if vm belongs to AutoScale vm group in Disabled state
autoScaleManager.checkIfVmActionAllowed(vmId);

Expand Down Expand Up @@ -8460,7 +8471,7 @@ public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {EnableDynamicallyScaleVm, AllowDiskOfferingChangeDuringScaleVm, AllowUserExpungeRecoverVm, VmIpFetchWaitInterval, VmIpFetchTrialMax,
VmIpFetchThreadPoolMax, VmIpFetchTaskWorkers, AllowDeployVmIfGivenHostFails, EnableAdditionalVmConfig, DisplayVMOVFProperties,
KvmAdditionalConfigAllowList, XenServerAdditionalConfigAllowList, VmwareAdditionalConfigAllowList, DestroyRootVolumeOnVmDestruction,
EnforceStrictResourceLimitHostTagCheck, StrictHostTags};
EnforceStrictResourceLimitHostTagCheck, StrictHostTags, AllowUserForceStopVm};
}

@Override
Expand Down
5 changes: 4 additions & 1 deletion ui/src/config/section/compute.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@ export default {
dataView: true,
groupAction: true,
groupMap: (selection, values) => { return selection.map(x => { return { id: x, forced: values.forced } }) },
args: ['forced'],
args: (record, store, group) => {
return (['Admin'].includes(store.userInfo.roletype) || store.features.allowuserforcestopvm)
? ['forced'] : []
},
show: (record) => { return ['Running'].includes(record.state) }
},
{
Expand Down