diff --git a/api/src/main/java/com/cloud/vm/VmDetailConstants.java b/api/src/main/java/com/cloud/vm/VmDetailConstants.java index f24c4f587c40..84de8c9ebac8 100644 --- a/api/src/main/java/com/cloud/vm/VmDetailConstants.java +++ b/api/src/main/java/com/cloud/vm/VmDetailConstants.java @@ -17,14 +17,41 @@ package com.cloud.vm; public interface VmDetailConstants { - public static final String KEYBOARD = "keyboard"; - public static final String NIC_ADAPTER = "nicAdapter"; - public static final String ROOT_DISK_CONTROLLER = "rootDiskController"; - public static final String NESTED_VIRTUALIZATION_FLAG = "nestedVirtualizationFlag"; - public static final String HYPERVISOR_TOOLS_VERSION = "hypervisortoolsversion"; - public static final String DATA_DISK_CONTROLLER = "dataDiskController"; - public static final String SVGA_VRAM_SIZE = "svga.vramSize"; - public static final String CPU_NUMBER = "cpuNumber"; - public static final String CPU_SPEED = "cpuSpeed"; - public static final String MEMORY = "memory"; + String KEYBOARD = "keyboard"; + String CPU_CORE_PER_SOCKET = "cpu.corespersocket"; + String ROOT_DISK_SIZE = "rootdisksize"; + + // VMware specific + String NIC_ADAPTER = "nicAdapter"; + String ROOT_DISK_CONTROLLER = "rootDiskController"; + String DATA_DISK_CONTROLLER = "dataDiskController"; + String SVGA_VRAM_SIZE = "svga.vramSize"; + String NESTED_VIRTUALIZATION_FLAG = "nestedVirtualizationFlag"; + + // XenServer specific (internal) + String HYPERVISOR_TOOLS_VERSION = "hypervisortoolsversion"; + String PLATFORM = "platform"; + String TIME_OFFSET = "timeoffset"; + + // KVM specific (internal) + String KVM_VNC_PORT = "kvm.vnc.port"; + String KVM_VNC_ADDRESS = "kvm.vnc.address"; + + // Mac OSX guest specific (internal) + String SMC_PRESENT = "smc.present"; + String FIRMWARE = "firmware"; + + // VM deployment with custom compute offering params + String CPU_NUMBER = "cpuNumber"; + String CPU_SPEED = "cpuSpeed"; + String MEMORY = "memory"; + + // Misc details for internal usage (not to be set/changed by user or admin) + String CPU_OVER_COMMIT_RATIO = "cpuOvercommitRatio"; + String MEMORY_OVER_COMMIT_RATIO = "memoryOvercommitRatio"; + String MESSAGE_RESERVED_CAPACITY_FREED_FLAG = "Message.ReservedCapacityFreed.Flag"; + String DEPLOY_VM = "deployvm"; + String SSH_PUBLIC_KEY = "SSH.PublicKey"; + String PASSWORD = "password"; + String ENCRYPTED_PASSWORD = "Encrypted.Password"; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListDetailOptionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListDetailOptionsCmd.java new file mode 100644 index 000000000000..e53754c099ca --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListDetailOptionsCmd.java @@ -0,0 +1,91 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// 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 +// +// 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 +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.resource; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.DetailOptionsResponse; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.server.ResourceTag; +import com.google.common.base.Strings; + +@APICommand(name = ListDetailOptionsCmd.APINAME, + description = "Lists all possible details and their options for a resource type such as a VM or a template", + responseObject = DetailOptionsResponse.class, + since = "4.13", + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ListDetailOptionsCmd extends BaseCmd { + public final static String APINAME = "listDetailOptions"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, required = true, + description = "the resource type such as UserVm, Template etc.", + validations = {ApiArgValidator.NotNullOrEmpty} + ) + private String resourceType; + + @Parameter(name = ApiConstants.RESOURCE_ID, type = CommandType.STRING, + description = "the UUID of the resource (optional)") + private String resourceId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public ResourceTag.ResourceObjectType getResourceType() { + return _taggedResourceService.getResourceType(resourceType); + } + + public String getResourceId() { + if (!Strings.isNullOrEmpty(resourceId)) { + return _taggedResourceService.getUuid(resourceId, getResourceType()); + } + return null; + } + + ///////////////////////////////////////////////////// + /////////////////// Implementation ////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } + + @Override + public void execute() { + final DetailOptionsResponse response = _queryService.listDetailOptions(this); + response.setResponseName(getCommandName()); + response.setObjectName("detailoptions"); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DetailOptionsResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DetailOptionsResponse.java new file mode 100644 index 000000000000..5f6bff395976 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/DetailOptionsResponse.java @@ -0,0 +1,44 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// 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 +// +// 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 +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class DetailOptionsResponse extends BaseResponse { + @SerializedName(ApiConstants.DETAILS) + @Param(description = "Map of all possible details and their possible list of values") + private Map> details; + + public DetailOptionsResponse(Map> details) { + this.details = details; + } + + public void setDetails(Map> details) { + this.details = details; + } + + public Map> getDetails() { + return details; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/query/QueryService.java b/api/src/main/java/org/apache/cloudstack/query/QueryService.java index 1f0f933d7d21..618a8f6f8a5a 100644 --- a/api/src/main/java/org/apache/cloudstack/query/QueryService.java +++ b/api/src/main/java/org/apache/cloudstack/query/QueryService.java @@ -20,8 +20,8 @@ import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.command.admin.domain.ListDomainsCmd; -import org.apache.cloudstack.api.command.admin.host.ListHostsCmd; import org.apache.cloudstack.api.command.admin.host.ListHostTagsCmd; +import org.apache.cloudstack.api.command.admin.host.ListHostsCmd; import org.apache.cloudstack.api.command.admin.internallb.ListInternalLBVMsCmd; import org.apache.cloudstack.api.command.admin.management.ListMgmtsCmd; import org.apache.cloudstack.api.command.admin.router.ListRoutersCmd; @@ -40,6 +40,7 @@ import org.apache.cloudstack.api.command.user.offering.ListServiceOfferingsCmd; import org.apache.cloudstack.api.command.user.project.ListProjectInvitationsCmd; import org.apache.cloudstack.api.command.user.project.ListProjectsCmd; +import org.apache.cloudstack.api.command.user.resource.ListDetailOptionsCmd; import org.apache.cloudstack.api.command.user.securitygroup.ListSecurityGroupsCmd; import org.apache.cloudstack.api.command.user.tag.ListTagsCmd; import org.apache.cloudstack.api.command.user.template.ListTemplatesCmd; @@ -50,6 +51,7 @@ import org.apache.cloudstack.api.command.user.zone.ListZonesCmd; import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.AsyncJobResponse; +import org.apache.cloudstack.api.response.DetailOptionsResponse; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.DomainRouterResponse; @@ -147,6 +149,8 @@ public interface QueryService { ListResponse listIsos(ListIsosCmd cmd); + DetailOptionsResponse listDetailOptions(ListDetailOptionsCmd cmd); + ListResponse searchForAffinityGroups(ListAffinityGroupsCmd cmd); List listResourceDetails(ListResourceDetailsCmd cmd); diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index 354323ecd2f2..d0ce294a5e9a 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -1070,16 +1070,16 @@ public void orchestrateStart(final String vmUuid, final Map 1f || Float.parseFloat(cluster_detail_ram.getValue()) > 1f)) { - userVmDetailsDao.addDetail(vm.getId(), "cpuOvercommitRatio", cluster_detail_cpu.getValue(), true); - userVmDetailsDao.addDetail(vm.getId(), "memoryOvercommitRatio", cluster_detail_ram.getValue(), true); - } else if (userVmDetailsDao.findDetail(vm.getId(), "cpuOvercommitRatio") != null) { - userVmDetailsDao.addDetail(vm.getId(), "cpuOvercommitRatio", cluster_detail_cpu.getValue(), true); - userVmDetailsDao.addDetail(vm.getId(), "memoryOvercommitRatio", cluster_detail_ram.getValue(), true); + userVmDetailsDao.addDetail(vm.getId(), VmDetailConstants.CPU_OVER_COMMIT_RATIO, cluster_detail_cpu.getValue(), true); + userVmDetailsDao.addDetail(vm.getId(), VmDetailConstants.MEMORY_OVER_COMMIT_RATIO, cluster_detail_ram.getValue(), true); + } else if (userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.CPU_OVER_COMMIT_RATIO) != null) { + userVmDetailsDao.addDetail(vm.getId(), VmDetailConstants.CPU_OVER_COMMIT_RATIO, cluster_detail_cpu.getValue(), true); + userVmDetailsDao.addDetail(vm.getId(), VmDetailConstants.MEMORY_OVER_COMMIT_RATIO, cluster_detail_ram.getValue(), true); } vmProfile.setCpuOvercommitRatio(Float.parseFloat(cluster_detail_cpu.getValue())); @@ -1161,8 +1161,8 @@ public void orchestrateStart(final String vmUuid, final Map vmMetadatum) { private void updateVmMetaData(Long vmId, String platform) { UserVmVO userVm = _userVmDao.findById(vmId); _userVmDao.loadDetails(userVm); - if ( userVm.details.containsKey("timeoffset")) { - userVm.details.remove("timeoffset"); + if ( userVm.details.containsKey(VmDetailConstants.TIME_OFFSET)) { + userVm.details.remove(VmDetailConstants.TIME_OFFSET); } - userVm.setDetail("platform", platform); + userVm.setDetail(VmDetailConstants.PLATFORM, platform); String pvdriver = "xenserver56"; if ( platform.contains("device_id")) { pvdriver = "xenserver61"; } - if (!userVm.details.containsKey("hypervisortoolsversion") || !userVm.details.get("hypervisortoolsversion").equals(pvdriver)) { - userVm.setDetail("hypervisortoolsversion", pvdriver); + if (!userVm.details.containsKey(VmDetailConstants.HYPERVISOR_TOOLS_VERSION) || !userVm.details.get(VmDetailConstants.HYPERVISOR_TOOLS_VERSION).equals(pvdriver)) { + userVm.setDetail(VmDetailConstants.HYPERVISOR_TOOLS_VERSION, pvdriver); } _userVmDao.saveDetails(userVm); } diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java index abbe3243c6cc..3d6732c0fbeb 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -1874,7 +1874,7 @@ protected StartAnswer execute(StartCommand cmd) { // Check for multi-cores per socket settings int numCoresPerSocket = 1; - String coresPerSocket = vmSpec.getDetails().get("cpu.corespersocket"); + String coresPerSocket = vmSpec.getDetails().get(VmDetailConstants.CPU_CORE_PER_SOCKET); if (coresPerSocket != null) { String apiVersion = HypervisorHostHelper.getVcenterApiVersion(vmMo.getContext()); // Property 'numCoresPerSocket' is supported since vSphere API 5.0 diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java index ff7b0a3ab539..79a9fb229724 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java @@ -134,6 +134,7 @@ import com.cloud.utils.ssh.SshHelper; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.PowerState; +import com.cloud.vm.VmDetailConstants; import com.trilead.ssh2.SCPClient; import com.xensource.xenapi.Bond; import com.xensource.xenapi.Connection; @@ -1862,18 +1863,18 @@ protected void finalizeVmMetaData(final VM vm, final Connection conn, final Virt final Map details = vmSpec.getDetails(); if (details != null) { - final String platformstring = details.get("platform"); + final String platformstring = details.get(VmDetailConstants.PLATFORM); if (platformstring != null && !platformstring.isEmpty()) { final Map platform = StringUtils.stringToMap(platformstring); vm.setPlatform(conn, platform); } else { - final String timeoffset = details.get("timeoffset"); + final String timeoffset = details.get(VmDetailConstants.TIME_OFFSET); if (timeoffset != null) { final Map platform = vm.getPlatform(conn); - platform.put("timeoffset", timeoffset); + platform.put(VmDetailConstants.TIME_OFFSET, timeoffset); vm.setPlatform(conn, platform); } - final String coresPerSocket = details.get("cpu.corespersocket"); + final String coresPerSocket = details.get(VmDetailConstants.CPU_CORE_PER_SOCKET); if (coresPerSocket != null) { final Map platform = vm.getPlatform(conn); platform.put("cores-per-socket", coresPerSocket); @@ -1881,7 +1882,7 @@ protected void finalizeVmMetaData(final VM vm, final Connection conn, final Virt } } if (!BootloaderType.CD.equals(vmSpec.getBootloader())) { - final String xenservertoolsversion = details.get("hypervisortoolsversion"); + final String xenservertoolsversion = details.get(VmDetailConstants.HYPERVISOR_TOOLS_VERSION); if ((xenservertoolsversion == null || !xenservertoolsversion.equalsIgnoreCase("xenserver61")) && vmSpec.getGpuDevice() == null) { final Map platform = vm.getPlatform(conn); platform.remove("device_id"); diff --git a/server/src/main/java/com/cloud/api/ApiDBUtils.java b/server/src/main/java/com/cloud/api/ApiDBUtils.java index 22ed2437d4de..40ff827a9cd5 100644 --- a/server/src/main/java/com/cloud/api/ApiDBUtils.java +++ b/server/src/main/java/com/cloud/api/ApiDBUtils.java @@ -302,6 +302,7 @@ import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VmDetailConstants; import com.cloud.vm.VmStats; import com.cloud.vm.dao.ConsoleProxyDao; import com.cloud.vm.dao.DomainRouterDao; @@ -1454,7 +1455,7 @@ public static String getKeyPairName(String sshPublicKey) { } public static UserVmDetailVO findPublicKeyByVmId(long vmId) { - return s_userVmDetailsDao.findDetail(vmId, "SSH.PublicKey"); + return s_userVmDetailsDao.findDetail(vmId, VmDetailConstants.SSH_PUBLIC_KEY); } public static void getAutoScaleVmGroupPolicies(long vmGroupId, List scaleUpPolicies, List scaleDownPolicies) { diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 8f003185da26..f0596a40a1a1 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -17,11 +17,16 @@ package com.cloud.api.query; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.inject.Inject; @@ -61,6 +66,7 @@ import org.apache.cloudstack.api.command.user.offering.ListServiceOfferingsCmd; import org.apache.cloudstack.api.command.user.project.ListProjectInvitationsCmd; import org.apache.cloudstack.api.command.user.project.ListProjectsCmd; +import org.apache.cloudstack.api.command.user.resource.ListDetailOptionsCmd; import org.apache.cloudstack.api.command.user.securitygroup.ListSecurityGroupsCmd; import org.apache.cloudstack.api.command.user.tag.ListTagsCmd; import org.apache.cloudstack.api.command.user.template.ListTemplatesCmd; @@ -71,6 +77,7 @@ import org.apache.cloudstack.api.command.user.zone.ListZonesCmd; import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.AsyncJobResponse; +import org.apache.cloudstack.api.response.DetailOptionsResponse; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.DomainRouterResponse; @@ -212,13 +219,16 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Func; import com.cloud.utils.db.SearchCriteria.Op; +import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.DomainRouterVO; import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; +import com.google.common.base.Strings; @Component public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements QueryService, Configurable { @@ -3381,6 +3391,61 @@ private Pair, Integer> searchForIsosInternal(ListIsosCmd cm hypervisorType, true, cmd.listInReadyState(), permittedAccounts, caller, listProjectResourcesCriteria, tags, showRemovedISO, null, null); } + @Override + public DetailOptionsResponse listDetailOptions(final ListDetailOptionsCmd cmd) { + final ResourceObjectType type = cmd.getResourceType(); + final String resourceUuid = cmd.getResourceId(); + final Map> options = new HashMap<>(); + switch (type) { + case Template: + case UserVm: + HypervisorType hypervisorType = HypervisorType.None; + if (!Strings.isNullOrEmpty(resourceUuid) && ResourceObjectType.Template.equals(type)) { + hypervisorType = _templateDao.findByUuid(resourceUuid).getHypervisorType(); + } + if (!Strings.isNullOrEmpty(resourceUuid) && ResourceObjectType.UserVm.equals(type)) { + hypervisorType = _vmInstanceDao.findByUuid(resourceUuid).getHypervisorType(); + } + fillVMOrTemplateDetailOptions(options, hypervisorType); + break; + default: + throw new CloudRuntimeException("Resource type not supported."); + } + if (CallContext.current().getCallingAccount().getType() != Account.ACCOUNT_TYPE_ADMIN) { + final List userBlacklistedSettings = Stream.of(QueryService.UserVMBlacklistedDetails.value().split(",")) + .map(item -> (item).trim()) + .collect(Collectors.toList()); + for (final String detail : userBlacklistedSettings) { + if (options.containsKey(detail)) { + options.remove(detail); + } + } + } + return new DetailOptionsResponse(options); + } + + private void fillVMOrTemplateDetailOptions(final Map> options, final HypervisorType hypervisorType) { + if (options == null) { + throw new CloudRuntimeException("Invalid/null detail-options response object passed"); + } + + options.put(VmDetailConstants.KEYBOARD, Arrays.asList("uk", "us", "jp", "fr")); + options.put(VmDetailConstants.CPU_CORE_PER_SOCKET, Collections.emptyList()); + options.put(VmDetailConstants.ROOT_DISK_SIZE, Collections.emptyList()); + + if (HypervisorType.KVM.equals(hypervisorType)) { + options.put(VmDetailConstants.ROOT_DISK_CONTROLLER, Arrays.asList("osdefault", "ide", "scsi", "virtio")); + } + + if (HypervisorType.VMware.equals(hypervisorType)) { + options.put(VmDetailConstants.NIC_ADAPTER, Arrays.asList("E1000", "PCNet32", "Vmxnet2", "Vmxnet3")); + options.put(VmDetailConstants.ROOT_DISK_CONTROLLER, Arrays.asList("osdefault", "ide", "scsi", "lsilogic", "lsisas1068", "buslogic", "pvscsi")); + options.put(VmDetailConstants.DATA_DISK_CONTROLLER, Arrays.asList("osdefault", "ide", "scsi", "lsilogic", "lsisas1068", "buslogic", "pvscsi")); + options.put(VmDetailConstants.NESTED_VIRTUALIZATION_FLAG, Arrays.asList("true", "false")); + options.put(VmDetailConstants.SVGA_VRAM_SIZE, Collections.emptyList()); + } + } + @Override public ListResponse searchForAffinityGroups(ListAffinityGroupsCmd cmd) { Pair, Integer> result = searchForAffinityGroupsInternal(cmd); @@ -3687,7 +3752,7 @@ public ListResponse listManagementServers(ListMgmtsCmd } response.setResponses(result); return response; - } + } @Override public String getConfigComponentName() { diff --git a/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java b/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java index 6775bfc6cff0..a7fee9603a07 100644 --- a/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java +++ b/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java @@ -89,6 +89,7 @@ import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.Event; import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.VMInstanceDao; @@ -138,8 +139,6 @@ public class CapacityManagerImpl extends ManagerBase implements CapacityManager, @Inject MessageBus _messageBus; - private static final String MESSAGE_RESERVED_CAPACITY_FREED_FLAG = "Message.ReservedCapacityFreed.Flag"; - @Override public boolean configure(String name, Map params) throws ConfigurationException { _vmCapacityReleaseInterval = NumbersUtil.parseInt(_configDao.getValue(Config.CapacitySkipcountingHours.key()), 3600); @@ -638,8 +637,8 @@ public void updateCapacityForHost(final Host host) { Float ramOvercommitRatio = 1.0f; long secondsSinceLastUpdate = (DateUtil.currentGMTTime().getTime() - vm.getUpdateTime().getTime()) / 1000; if (secondsSinceLastUpdate < _vmCapacityReleaseInterval) { - UserVmDetailVO vmDetailCpu = _userVmDetailsDao.findDetail(vm.getId(), "cpuOvercommitRatio"); - UserVmDetailVO vmDetailRam = _userVmDetailsDao.findDetail(vm.getId(), "memoryOvercommitRatio"); + UserVmDetailVO vmDetailCpu = _userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.CPU_OVER_COMMIT_RATIO); + UserVmDetailVO vmDetailRam = _userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.MEMORY_OVER_COMMIT_RATIO); if (vmDetailCpu != null) { //if vmDetail_cpu is not null it means it is running in a overcommited cluster. cpuOvercommitRatio = Float.parseFloat(vmDetailCpu.getValue()); @@ -669,14 +668,14 @@ public void updateCapacityForHost(final Host host) { } else { // signal if not done already, that the VM has been stopped for skip.counting.hours, // hence capacity will not be reserved anymore. - UserVmDetailVO messageSentFlag = _userVmDetailsDao.findDetail(vm.getId(), MESSAGE_RESERVED_CAPACITY_FREED_FLAG); + UserVmDetailVO messageSentFlag = _userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.MESSAGE_RESERVED_CAPACITY_FREED_FLAG); if (messageSentFlag == null || !Boolean.valueOf(messageSentFlag.getValue())) { _messageBus.publish(_name, "VM_ReservedCapacity_Free", PublishScope.LOCAL, vm); if (vm.getType() == VirtualMachine.Type.User) { UserVmVO userVM = _userVMDao.findById(vm.getId()); _userVMDao.loadDetails(userVM); - userVM.setDetail(MESSAGE_RESERVED_CAPACITY_FREED_FLAG, "true"); + userVM.setDetail(VmDetailConstants.MESSAGE_RESERVED_CAPACITY_FREED_FLAG, "true"); _userVMDao.saveDetails(userVM); } } @@ -903,7 +902,7 @@ public boolean postStateTransitionEvent(StateMachine2.Transition t UserVmVO userVM = _userVMDao.findById(vm.getId()); _userVMDao.loadDetails(userVM); // free the message sent flag if it exists - userVM.setDetail(MESSAGE_RESERVED_CAPACITY_FREED_FLAG, "false"); + userVM.setDetail(VmDetailConstants.MESSAGE_RESERVED_CAPACITY_FREED_FLAG, "false"); _userVMDao.saveDetails(userVM); } diff --git a/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java b/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java index 4d2452d45ce5..76e4fc03ce7b 100644 --- a/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java +++ b/server/src/main/java/com/cloud/network/element/ConfigDriveNetworkElement.java @@ -77,6 +77,7 @@ import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.UserVmDetailsDao; @@ -195,7 +196,7 @@ public boolean canEnableIndividualServices() { } private String getSshKey(VirtualMachineProfile profile) { - final UserVmDetailVO vmDetailSshKey = _userVmDetailsDao.findDetail(profile.getId(), "SSH.PublicKey"); + final UserVmDetailVO vmDetailSshKey = _userVmDetailsDao.findDetail(profile.getId(), VmDetailConstants.SSH_PUBLIC_KEY); return (vmDetailSshKey!=null ? vmDetailSshKey.getValue() : null); } @@ -262,7 +263,7 @@ private void storePasswordInVmDetails(VirtualMachineProfile vm) { final String password_encrypted = DBEncryptionUtil.encrypt(password); final UserVmVO userVmVO = _userVmDao.findById(vm.getId()); - _userVmDetailsDao.addDetail(vm.getId(), "password", password_encrypted, false); + _userVmDetailsDao.addDetail(vm.getId(), VmDetailConstants.PASSWORD, password_encrypted, false); userVmVO.setUpdateParameters(true); _userVmDao.update(userVmVO.getId(), userVmVO); diff --git a/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java b/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java index b78dcfdbb5da..aefa528bea7c 100644 --- a/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java +++ b/server/src/main/java/com/cloud/network/element/VirtualRouterElement.java @@ -31,6 +31,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import com.cloud.vm.VmDetailConstants; import com.google.gson.Gson; import org.apache.cloudstack.api.command.admin.router.ConfigureOvsElementCmd; @@ -718,7 +719,7 @@ public boolean savePassword(final Network network, final NicProfile nic, final V final UserVmVO userVmVO = _userVmDao.findById(vm.getId()); _userVmDao.loadDetails(userVmVO); - userVmVO.setDetail("password", password_encrypted); + userVmVO.setDetail(VmDetailConstants.PASSWORD, password_encrypted); _userVmDao.saveDetails(userVmVO); userVmVO.setUpdateParameters(true); diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java index 27fa42cc4c9f..d07a4383d015 100755 --- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java +++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java @@ -31,13 +31,6 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.utils.Pair; -import com.cloud.vm.dao.UserVmDetailsDao; -import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.commons.lang.ObjectUtils; -import org.apache.log4j.Logger; -import org.springframework.stereotype.Component; - import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.cluster.AddClusterCmd; import org.apache.cloudstack.api.command.admin.cluster.DeleteClusterCmd; @@ -49,21 +42,25 @@ import org.apache.cloudstack.api.command.admin.host.UpdateHostCmd; import org.apache.cloudstack.api.command.admin.host.UpdateHostPasswordCmd; import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.utils.identity.ManagementServerNode; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.ObjectUtils; +import org.apache.log4j.Logger; +import org.springframework.stereotype.Component; import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; -import com.cloud.agent.api.GetVncPortCommand; -import com.cloud.agent.api.GetVncPortAnswer; import com.cloud.agent.api.GetGPUStatsAnswer; import com.cloud.agent.api.GetGPUStatsCommand; import com.cloud.agent.api.GetHostStatsAnswer; import com.cloud.agent.api.GetHostStatsCommand; +import com.cloud.agent.api.GetVncPortAnswer; +import com.cloud.agent.api.GetVncPortCommand; import com.cloud.agent.api.MaintainAnswer; import com.cloud.agent.api.MaintainCommand; import com.cloud.agent.api.PropagateResourceEventCommand; @@ -148,6 +145,7 @@ import com.cloud.storage.dao.VMTemplateDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; +import com.cloud.utils.Pair; import com.cloud.utils.StringUtils; import com.cloud.utils.UriUtils; import com.cloud.utils.component.Manager; @@ -177,6 +175,8 @@ import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.VmDetailConstants; +import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.VMInstanceDao; import com.google.gson.Gson; @@ -1314,8 +1314,8 @@ protected void setKVMVncAccess(long hostId, List vms) { for (VMInstanceVO vm : vms) { GetVncPortAnswer vmVncPortAnswer = (GetVncPortAnswer) _agentMgr.easySend(hostId, new GetVncPortCommand(vm.getId(), vm.getInstanceName())); if (vmVncPortAnswer != null) { - userVmDetailsDao.addDetail(vm.getId(), "kvm.vnc.address", vmVncPortAnswer.getAddress(), true); - userVmDetailsDao.addDetail(vm.getId(), "kvm.vnc.port", String.valueOf(vmVncPortAnswer.getPort()), true); + userVmDetailsDao.addDetail(vm.getId(), VmDetailConstants.KVM_VNC_ADDRESS, vmVncPortAnswer.getAddress(), true); + userVmDetailsDao.addDetail(vm.getId(), VmDetailConstants.KVM_VNC_PORT, String.valueOf(vmVncPortAnswer.getPort()), true); } } } diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 2c5892ed70e6..1d921e542762 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -409,6 +409,7 @@ import org.apache.cloudstack.api.command.user.region.ha.gslb.RemoveFromGlobalLoadBalancerRuleCmd; import org.apache.cloudstack.api.command.user.region.ha.gslb.UpdateGlobalLoadBalancerRuleCmd; import org.apache.cloudstack.api.command.user.resource.GetCloudIdentifierCmd; +import org.apache.cloudstack.api.command.user.resource.ListDetailOptionsCmd; import org.apache.cloudstack.api.command.user.resource.ListHypervisorsCmd; import org.apache.cloudstack.api.command.user.resource.ListResourceLimitsCmd; import org.apache.cloudstack.api.command.user.resource.UpdateResourceCountCmd; @@ -2878,6 +2879,7 @@ public List> getCommands() { cmdList.add(ExtractTemplateCmd.class); cmdList.add(ListTemplatePermissionsCmd.class); cmdList.add(ListTemplatesCmd.class); + cmdList.add(ListDetailOptionsCmd.class); cmdList.add(RegisterTemplateCmd.class); cmdList.add(UpdateTemplateCmd.class); cmdList.add(UpdateTemplatePermissionsCmd.class); diff --git a/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java b/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java index 8cfaa9fd69b2..5a6c84f14795 100644 --- a/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java +++ b/server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java @@ -41,6 +41,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.context.support.SpringBeanAutowiringSupport; +import com.cloud.vm.VmDetailConstants; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -421,8 +422,8 @@ private String composeConsoleAccessUrl(String rootUrl, VirtualMachine vm, HostVO Pair portInfo; if (hostVo.getResourceState().equals(ResourceState.ErrorInMaintenance)) { - UserVmDetailVO detailAddress = _userVmDetailsDao.findDetail(vm.getId(), "kvm.vnc.address"); - UserVmDetailVO detailPort = _userVmDetailsDao.findDetail(vm.getId(), "kvm.vnc.port"); + UserVmDetailVO detailAddress = _userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.KVM_VNC_ADDRESS); + UserVmDetailVO detailPort = _userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.KVM_VNC_PORT); portInfo = new Pair<>(detailAddress.getValue(), Integer.valueOf(detailPort.getValue())); } else { portInfo = _ms.getVncPort(vm); @@ -441,7 +442,7 @@ private String composeConsoleAccessUrl(String rootUrl, VirtualMachine vm, HostVO } String sid = vm.getVncPassword(); - UserVmDetailVO details = _userVmDetailsDao.findDetail(vm.getId(), "keyboard"); + UserVmDetailVO details = _userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.KEYBOARD); String tag = vm.getUuid(); diff --git a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java index 9beeb7bc0108..5eb96aac11df 100755 --- a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java @@ -39,6 +39,7 @@ import com.cloud.utils.DateUtil; import com.cloud.utils.Pair; import com.cloud.utils.EnumUtils; +import com.cloud.vm.VmDetailConstants; import com.google.common.base.Joiner; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -1918,7 +1919,7 @@ public VMTemplateVO createPrivateTemplateRecord(CreateTemplateCmd cmd, Account t } } if (cmd.getDetails() != null) { - details.remove("Encrypted.Password"); // new password will be generated during vm deployment from password enabled template + details.remove(VmDetailConstants.ENCRYPTED_PASSWORD); // new password will be generated during vm deployment from password enabled template details.putAll(cmd.getDetails()); } if (!details.isEmpty()) { diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 8992351d18d9..b89a8028a858 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -35,6 +35,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.inject.Inject; import javax.naming.ConfigurationException; @@ -85,6 +86,7 @@ import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.query.QueryService; import org.apache.cloudstack.storage.command.DeleteCommand; import org.apache.cloudstack.storage.command.DettachCommand; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; @@ -844,7 +846,7 @@ private boolean resetVMSSHKeyInternal(Long vmId, String sshPublicKey, String pas } else { final UserVmVO userVm = _vmDao.findById(vmId); _vmDao.loadDetails(userVm); - userVm.setDetail("SSH.PublicKey", sshPublicKey); + userVm.setDetail(VmDetailConstants.SSH_PUBLIC_KEY, sshPublicKey); if (template.isEnablePassword()) { userVm.setPassword(password); //update the encrypted password in vm_details table too @@ -2423,11 +2425,36 @@ public UserVm updateVirtualMachine(UpdateVMCmd cmd) throws ResourceUnavailableEx if (isDisplayVm != null && isDisplayVm != vmInstance.isDisplay()) { updateDisplayVmFlag(isDisplayVm, id, vmInstance); } + final Account caller = CallContext.current().getCallingAccount(); + final List userBlacklistedSettings = Stream.of(QueryService.UserVMBlacklistedDetails.value().split(",")) + .map(item -> (item).trim()) + .collect(Collectors.toList()); if (cleanupDetails){ - userVmDetailsDao.removeDetails(id); - } - else { + if (caller != null && caller.getType() == Account.ACCOUNT_TYPE_ADMIN) { + userVmDetailsDao.removeDetails(id); + } else { + for (final UserVmDetailVO detail : userVmDetailsDao.listDetails(id)) { + if (detail != null && !userBlacklistedSettings.contains(detail.getName())) { + userVmDetailsDao.removeDetail(id, detail.getName()); + } + } + } + } else { if (MapUtils.isNotEmpty(details)) { + if (caller != null && caller.getType() != Account.ACCOUNT_TYPE_ADMIN) { + // Ensure blacklisted detail is not passed by non-root-admin user + for (final String detailName : details.keySet()) { + if (userBlacklistedSettings.contains(detailName)) { + throw new InvalidParameterValueException("You're not allowed to add or edit the restricted setting: " + detailName); + } + } + // Add any hidden/blacklisted detail + for (final UserVmDetailVO detail : userVmDetailsDao.listDetails(id)) { + if (userBlacklistedSettings.contains(detail.getName())) { + details.put(detail.getName(), detail.getValue()); + } + } + } vmInstance.setDetails(details); _vmDao.saveDetails(vmInstance); } @@ -3376,13 +3403,13 @@ private UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffe boolean isIso = Storage.ImageFormat.ISO == template.getFormat(); long size = 0; // custom root disk size, resizes base template to larger size - if (customParameters.containsKey("rootdisksize")) { + if (customParameters.containsKey(VmDetailConstants.ROOT_DISK_SIZE)) { // only KVM, XenServer and VMware supports rootdisksize override if (!(hypervisorType == HypervisorType.KVM || hypervisorType == HypervisorType.XenServer || hypervisorType == HypervisorType.VMware || hypervisorType == HypervisorType.Simulator)) { throw new InvalidParameterValueException("Hypervisor " + hypervisorType + " does not support rootdisksize override"); } - Long rootDiskSize = NumbersUtil.parseLong(customParameters.get("rootdisksize"), -1); + Long rootDiskSize = NumbersUtil.parseLong(customParameters.get(VmDetailConstants.ROOT_DISK_SIZE), -1); if (rootDiskSize <= 0) { throw new InvalidParameterValueException("Root disk size should be a positive number."); } @@ -3765,7 +3792,7 @@ public UserVmVO doInTransaction(TransactionStatus status) throws InsufficientCap } if (sshPublicKey != null) { - vm.setDetail("SSH.PublicKey", sshPublicKey); + vm.setDetail(VmDetailConstants.SSH_PUBLIC_KEY, sshPublicKey); } if (keyboard != null && !keyboard.isEmpty()) { @@ -3777,9 +3804,9 @@ public UserVmVO doInTransaction(TransactionStatus status) throws InsufficientCap } Long rootDiskSize = null; // custom root disk size, resizes base template to larger size - if (customParameters.containsKey("rootdisksize")) { + if (customParameters.containsKey(VmDetailConstants.ROOT_DISK_SIZE)) { // already verified for positive number - rootDiskSize = Long.parseLong(customParameters.get("rootdisksize")); + rootDiskSize = Long.parseLong(customParameters.get(VmDetailConstants.ROOT_DISK_SIZE)); VMTemplateVO templateVO = _templateDao.findById(template.getId()); if (templateVO == null) { @@ -3803,10 +3830,10 @@ public UserVmVO doInTransaction(TransactionStatus status) throws InsufficientCap // If hypervisor is vSphere and OS is OS X, set special settings. if (hypervisorType.equals(HypervisorType.VMware)) { if (guestOS.getDisplayName().toLowerCase().contains("apple mac os")) { - vm.setDetail("smc.present", "TRUE"); + vm.setDetail(VmDetailConstants.SMC_PRESENT, "TRUE"); vm.setDetail(VmDetailConstants.ROOT_DISK_CONTROLLER, "scsi"); vm.setDetail(VmDetailConstants.DATA_DISK_CONTROLLER, "scsi"); - vm.setDetail("firmware", "efi"); + vm.setDetail(VmDetailConstants.FIRMWARE, "efi"); s_logger.info("guestOS is OSX : overwrite root disk controller to scsi, use smc and efi"); } else { String controllerSetting = _configDao.getValue("vmware.root.disk.controller"); @@ -3835,7 +3862,7 @@ public UserVmVO doInTransaction(TransactionStatus status) throws InsufficientCap vm.setDetail(key, customParameters.get(key)); } } - vm.setDetail("deployvm", "true"); + vm.setDetail(VmDetailConstants.DEPLOY_VM, "true"); _vmDao.saveDetails(vm); s_logger.debug("Allocating in the DB for vm"); @@ -3885,10 +3912,10 @@ public void validateRootDiskResize(final HypervisorType hypervisorType, Long roo s_logger.error(error); throw new InvalidParameterValueException(error); } else if ((rootDiskSize << 30) > templateVO.getSize()) { - if (hypervisorType == HypervisorType.VMware && (vm.getDetails() == null || vm.getDetails().get("rootDiskController") == null)) { + if (hypervisorType == HypervisorType.VMware && (vm.getDetails() == null || vm.getDetails().get(VmDetailConstants.ROOT_DISK_CONTROLLER) == null)) { s_logger.warn("If Root disk controller parameter is not overridden, then Root disk resize may fail because current Root disk controller value is NULL."); - } else if (hypervisorType == HypervisorType.VMware && !vm.getDetails().get("rootDiskController").toLowerCase().contains("scsi")) { - String error = "Found unsupported root disk controller: " + vm.getDetails().get("rootDiskController"); + } else if (hypervisorType == HypervisorType.VMware && !vm.getDetails().get(VmDetailConstants.ROOT_DISK_CONTROLLER).toLowerCase().contains("scsi")) { + String error = "Found unsupported root disk controller: " + vm.getDetails().get(VmDetailConstants.ROOT_DISK_CONTROLLER); s_logger.error(error); throw new InvalidParameterValueException(error); } else { @@ -3896,7 +3923,7 @@ public void validateRootDiskResize(final HypervisorType hypervisorType, Long roo } } else { s_logger.debug("Root disk size specified is " + (rootDiskSize << 30) + "B and Template root disk size is " + templateVO.getSize() + "B. Both are equal so no need to override"); - customParameters.remove("rootdisksize"); + customParameters.remove(VmDetailConstants.ROOT_DISK_SIZE); } } @@ -4179,7 +4206,7 @@ public boolean finalizeVirtualMachineProfile(VirtualMachineProfile profile, Depl boolean isWindows = _guestOSCategoryDao.findById(_guestOSDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows"); List vmData = _networkModel.generateVmData(vm.getUserData(), serviceOffering, vm.getDataCenterId(), vm.getInstanceName(), vm.getHostName(), vm.getId(), - vm.getUuid(), defaultNic.getIPv4Address(), vm.getDetail("SSH.PublicKey"), (String) profile.getParameter(VirtualMachineProfile.Param.VmPassword), isWindows); + vm.getUuid(), defaultNic.getIPv4Address(), vm.getDetail(VmDetailConstants.SSH_PUBLIC_KEY), (String) profile.getParameter(VirtualMachineProfile.Param.VmPassword), isWindows); String vmName = vm.getInstanceName(); String configDriveIsoRootFolder = "/tmp"; String isoFile = configDriveIsoRootFolder + "/" + vmName + "/configDrive/" + vmName + ".iso"; @@ -4571,8 +4598,8 @@ public Pair> startVirtualMach // this value is not being sent to the backend; need only for api // display purposes if (template.isEnablePassword()) { - if (vm.getDetail("password") != null) { - userVmDetailsDao.removeDetail(vm.getId(), "password"); + if (vm.getDetail(VmDetailConstants.PASSWORD) != null) { + userVmDetailsDao.removeDetail(vm.getId(), VmDetailConstants.PASSWORD); } vm.setUpdateParameters(false); _vmDao.update(vm.getId(), vm); @@ -6327,7 +6354,7 @@ public UserVm restoreVMInternal(Account caller, UserVmVO vm, Long newTemplateId) if (needRestart) { try { - if (vm.getDetail("password") != null) { + if (vm.getDetail(VmDetailConstants.PASSWORD) != null) { params = new HashMap(); params.put(VirtualMachineProfile.Param.VmPassword, password); } @@ -6340,8 +6367,8 @@ public UserVm restoreVMInternal(Account caller, UserVmVO vm, Long newTemplateId) if (vm.isUpdateParameters()) { vm.setUpdateParameters(false); _vmDao.loadDetails(vm); - if (vm.getDetail("password") != null) { - userVmDetailsDao.removeDetail(vm.getId(), "password"); + if (vm.getDetail(VmDetailConstants.PASSWORD) != null) { + userVmDetailsDao.removeDetail(vm.getId(), VmDetailConstants.PASSWORD); } _vmDao.update(vm.getId(), vm); } @@ -6519,7 +6546,7 @@ public void prepareStop(VirtualMachineProfile profile) { } private void encryptAndStorePassword(UserVmVO vm, String password) { - String sshPublicKey = vm.getDetail("SSH.PublicKey"); + String sshPublicKey = vm.getDetail(VmDetailConstants.SSH_PUBLIC_KEY); if (sshPublicKey != null && !sshPublicKey.equals("") && password != null && !password.equals("saved_password")) { if (!sshPublicKey.startsWith("ssh-rsa")) { s_logger.warn("Only RSA public keys can be used to encrypt a vm password."); @@ -6530,7 +6557,7 @@ private void encryptAndStorePassword(UserVmVO vm, String password) { throw new CloudRuntimeException("Error encrypting password"); } - vm.setDetail("Encrypted.Password", encryptedPasswd); + vm.setDetail(VmDetailConstants.ENCRYPTED_PASSWORD, encryptedPasswd); _vmDao.saveDetails(vm); } } @@ -6562,7 +6589,7 @@ public ConfigKey[] getConfigKeys() { public String getVmUserData(long vmId) { UserVmVO vm = _vmDao.findById(vmId); if (vm == null) { - throw new InvalidParameterValueException("Unable to find virual machine with id " + vmId); + throw new InvalidParameterValueException("Unable to find virtual machine with id " + vmId); } _accountMgr.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index 2744714c67ba..965377b2c7bc 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -22,6 +22,7 @@ import org.apache.cloudstack.api.BaseCmd.HTTPMethod; import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd; import org.apache.cloudstack.context.CallContext; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -44,6 +45,8 @@ import com.cloud.storage.dao.GuestOSDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; +import com.cloud.user.UserVO; import com.cloud.uservm.UserVm; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.UserVmDetailsDao; @@ -76,6 +79,12 @@ public class UserVmManagerImplTest { @Mock private NetworkModel networkModel; + @Mock + private AccountVO callerAccount; + + @Mock + private UserVO callerUser; + private long vmId = 1l; @Before @@ -83,6 +92,14 @@ public void beforeTest() { Mockito.when(updateVmCommand.getId()).thenReturn(vmId); Mockito.when(userVmDao.findById(Mockito.eq(vmId))).thenReturn(userVmVoMock); + + Mockito.when(callerAccount.getType()).thenReturn(Account.ACCOUNT_TYPE_ADMIN); + CallContext.register(callerUser, callerAccount); + } + + @After + public void afterTest() { + CallContext.unregister(); } @Test diff --git a/ui/css/cloudstack3.css b/ui/css/cloudstack3.css index b3b7490825f1..016af6bd695b 100644 --- a/ui/css/cloudstack3.css +++ b/ui/css/cloudstack3.css @@ -13013,6 +13013,29 @@ div.gpugroups div.list-view { background: transparent url("../images/icons.png") no-repeat -626px -209px; } +ul.ui-autocomplete.ui-menu { + width: 250px; + max-height: 100px; + background: #eee; + padding: 5px; + text-align: left; + overflow-y: auto; + overflow-x: hidden; + z-index: 100; +} + +.ui-menu .ui-menu-item { + cursor: pointer; +} + +.ui-menu .ui-menu-item .ui-state-active { + background: #CBDDF3; +} + +.ui-helper-hidden-accessible { + display: none; +} + .copy-template-destination-list div.text-search { right: 5px; } diff --git a/ui/scripts/globalSettings.js b/ui/scripts/globalSettings.js index ab03978c22a4..3e926ea678fc 100644 --- a/ui/scripts/globalSettings.js +++ b/ui/scripts/globalSettings.js @@ -168,7 +168,6 @@ }], dataProvider: function(args) { var items = []; - console.log(args); $.ajax({ url: createURL("listLdapConfigurations&hostname=" + args.context.ldapConfiguration[0].hostname), dataType: "json", diff --git a/ui/scripts/instances.js b/ui/scripts/instances.js index c0693f7ef98f..bdc87c944c7b 100644 --- a/ui/scripts/instances.js +++ b/ui/scripts/instances.js @@ -3224,6 +3224,7 @@ settings: { title: 'label.settings', custom: cloudStack.uiCustom.granularDetails({ + resourceType: 'UserVm', dataProvider: function(args) { $.ajax({ url: createURL('listVirtualMachines&id=' + args.context.instances[0].id), @@ -3283,7 +3284,6 @@ // It could happen that a stale web page has been opened up when VM was stopped but // vm was turned on through another route - UI or API. so we should check again. var existingDetails = virtualMachine.details; - console.log(existingDetails); var newDetails = {}; for (d in existingDetails) { if (d != data.name) { diff --git a/ui/scripts/roles.js b/ui/scripts/roles.js index 59072bd47c0a..ff89dc56a19b 100644 --- a/ui/scripts/roles.js +++ b/ui/scripts/roles.js @@ -327,31 +327,24 @@ }); } }); - var setupAutocompletion = function() { - var $target = $($.find('input[name="rule"]')); - if ($target.hasClass('ui-autocomplete')) { - $target.autocomplete('destroy'); + $.ajax({ + url: createURL("listApis"), + dataType: "json", + success: function(json) { + var apis = []; + var response = json.listapisresponse.api; + $.each(response, function(idx, api) { + apis.push(api.name); + }); + $($.find('input[name="rule"]')).autocomplete({ + minLength: 0, + delay: 0, + source: apis.sort() + }).focus(function() { + $(this).data("uiAutocomplete").search($(this).val()); + }); } - $($.find('input[name="rule"]')).autocomplete({ - source: apiList, - autoFocus:true - }); - }; - if (apiList.length == 0) { - $.ajax({ - url: createURL("listApis"), - dataType: "json", - success: function(json) { - var response = json.listapisresponse.api; - $.each(response, function(idx, api) { - apiList.push(api.name); - }); - setupAutocompletion(); - } - }); - } else { - setupAutocompletion(); - } + }); } }); } diff --git a/ui/scripts/templates.js b/ui/scripts/templates.js index 28af9d39f2fd..df040001edcf 100755 --- a/ui/scripts/templates.js +++ b/ui/scripts/templates.js @@ -2170,6 +2170,7 @@ settings: { title: 'label.settings', custom: cloudStack.uiCustom.granularDetails({ + resourceType: 'Template', dataProvider: function(args) { $.ajax({ url: createURL('listTemplates'), diff --git a/ui/scripts/ui-custom/granularSettings.js b/ui/scripts/ui-custom/granularSettings.js index 5312394127e6..42177e321563 100644 --- a/ui/scripts/ui-custom/granularSettings.js +++ b/ui/scripts/ui-custom/granularSettings.js @@ -54,9 +54,10 @@ return $listView; } }; - cloudStack.uiCustom.granularDetails = function(args) { + cloudStack.uiCustom.granularDetails = function(args) { var dataProvider = args.dataProvider; var actions = args.actions; + var resourceType = args.resourceType; return function(args) { var context = args.context; @@ -77,57 +78,141 @@ label: 'label.change.value', action: actions.edit }, - remove: { - label: 'Remove Setting', - messages: { - confirm: function(args) { - return 'Delete Setting'; - }, - notification: function(args) { - return 'Setting deleted'; - } - }, - action: actions.remove, - notification: { - poll: function(args) { - args.complete(); - } - } - }, - add : { - label: 'Add Setting', - messages: { - confirm: function(args) { - return 'Add Setting'; - }, - notification: function(args) { - return 'Setting added'; - } - }, - preFilter: function(args) { - return true; - }, - createForm: { - title: 'Add New Setting', - fields: { - name: { - label: 'label.name', - validation: { - required: true - } - }, - value: { - label: 'label.value', - validation: { - required: true - } - } - } - }, - action: actions.add - } + remove: { + label: 'Remove Setting', + messages: { + confirm: function(args) { + return 'Delete Setting'; + }, + notification: function(args) { + return 'Setting deleted'; + } + }, + action: actions.remove, + notification: { + poll: function(args) { + args.complete(); + } + } + }, + add : { + label: 'Add Setting', + messages: { + confirm: function(args) { + return 'Add Setting'; + }, + notification: function(args) { + return 'Setting added'; + } + }, + preFilter: function(args) { + return true; + }, + createForm: { + title: 'Add New Setting', + preFilter: function(args) { + var data = { + resourcetype: resourceType + }; + if (resourceType === 'UserVm') { + data.resourceid = args.context.instances[0].id; + } + if (resourceType === 'Template') { + data.resourceid = args.context.templates[0].id; + } + + $.ajax({ + url: createURL("listDetailOptions"), + data: data, + dataType: "json", + success: function(json) { + var details = json.listdetailoptionsresponse.detailoptions.details; + var keys = []; + Object.keys(details).forEach(function(key,index) { + keys.push(key); + }); + $(args.$form.find('input[name="name"]')).blur(); + $(args.$form.find('input[name="name"]')).autocomplete({ + minLength: 0, + delay: 0, + source: keys.sort(), + select: function(event, ui) { + const key = ui.item.value; + const options = details[key]; + $(args.$form.find('input[name="value"]')).autocomplete({ + minLength: 0, + delay: 0, + autoFocus: true, + source: options.sort() + }).focus(function() { + $(this).data("uiAutocomplete").search($(this).val()); + }); + } + }).focus(function() { + $(this).data("uiAutocomplete").search($(this).val()); + }); + } + }); + + return true; + }, + fields: { + name: { + label: 'label.name', + validation: { + required: true + } + }, + value: { + label: 'label.value', + validation: { + required: true + } + } + } + }, + action: actions.add + } }, - dataProvider: dataProvider + dataProvider: function(args) { + var data = { + resourcetype: resourceType + }; + if (resourceType === 'UserVm') { + data.resourceid = args.context.instances[0].id; + } + if (resourceType === 'Template') { + data.resourceid = args.context.templates[0].id; + } + + $.ajax({ + url: createURL("listDetailOptions"), + data: data, + dataType: "json", + success: function(json) { + var details = json.listdetailoptionsresponse.detailoptions.details; + var tbody = $.find('#details-tab-settings .data-table tbody'); + $(tbody).on('DOMNodeInserted', "tr", function() { + $.each($.find('#details-tab-settings .data-table tbody tr'), function(idx, row) { + const key = $(row).find('td.name').attr('title'); + const options = details[key]; + if (options) { + $($(row).find('input.edit')).autocomplete({ + minLength: 0, + delay: 0, + autoFocus: true, + source: options.sort() + }).focus(function() { + $(this).data("uiAutocomplete").search(""); + }); + $(row).find('input.edit').blur(); + } + }); + }); + dataProvider(args); + } + }); + } }; var $listView = $('
').listView({ @@ -138,4 +223,4 @@ return $listView; } }; -}(jQuery, cloudStack)); \ No newline at end of file +}(jQuery, cloudStack));