diff --git a/api/src/main/java/com/cloud/vm/UserVmService.java b/api/src/main/java/com/cloud/vm/UserVmService.java index 8a1d6155e982..2f6888a54bb4 100644 --- a/api/src/main/java/com/cloud/vm/UserVmService.java +++ b/api/src/main/java/com/cloud/vm/UserVmService.java @@ -215,7 +215,7 @@ UserVm startVirtualMachine(StartVMCmd cmd) throws StorageUnavailableException, E */ UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, - String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIp, Boolean displayVm, String keyboard, + String userData, List sshKeyPairs, Map requestedIps, IpAddresses defaultIp, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameter, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId) throws InsufficientCapacityException, @@ -297,7 +297,7 @@ UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering s */ UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, - HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, + HTTPMethod httpmethod, String userData, List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId) throws InsufficientCapacityException, @@ -377,7 +377,7 @@ UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOfferin */ UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, - String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, + List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, Map templateOvfPropertiesMap, boolean dynamicScalingEnabled, String type, Long overrideDiskOfferingId) diff --git a/api/src/main/java/com/cloud/vm/VmDetailConstants.java b/api/src/main/java/com/cloud/vm/VmDetailConstants.java index 5fbbc872d341..45dc7a2946f4 100644 --- a/api/src/main/java/com/cloud/vm/VmDetailConstants.java +++ b/api/src/main/java/com/cloud/vm/VmDetailConstants.java @@ -59,6 +59,7 @@ public interface VmDetailConstants { String MESSAGE_RESERVED_CAPACITY_FREED_FLAG = "Message.ReservedCapacityFreed.Flag"; String DEPLOY_VM = "deployvm"; String SSH_PUBLIC_KEY = "SSH.PublicKey"; + String SSH_KEY_PAIR_NAMES = "SSH.KeyPairNames"; String PASSWORD = "password"; String ENCRYPTED_PASSWORD = "Encrypted.Password"; @@ -73,5 +74,6 @@ public interface VmDetailConstants { String DISK_OFFERING = "diskOffering"; String DEPLOY_AS_IS_CONFIGURATION = "configurationId"; + String KEY_PAIR_NAMES = "keypairnames"; String CKS_CONTROL_NODE_LOGIN_USER = "controlNodeLoginUser"; } diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 49444d554dc7..f2379834a412 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -441,6 +441,7 @@ public class ApiConstants { public static final String NETWORKRATE = "networkrate"; public static final String HOST_TAGS = "hosttags"; public static final String SSH_KEYPAIR = "keypair"; + public static final String SSH_KEYPAIRS = "keypairs"; public static final String HTTPMETHOD = "httpmethod"; public static final String HOST_CPU_CAPACITY = "hostcpucapacity"; public static final String HOST_CPU_NUM = "hostcpunum"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index 90b999d269d0..41411b0be69f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -153,9 +153,13 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG length = 1048576) private String userData; + @Deprecated @Parameter(name = ApiConstants.SSH_KEYPAIR, type = CommandType.STRING, description = "name of the ssh key pair used to login to the virtual machine") private String sshKeyPairName; + @Parameter(name = ApiConstants.SSH_KEYPAIRS, type = CommandType.LIST, collectionType = CommandType.STRING, since="4.17", description = "names of the ssh key pairs used to login to the virtual machine") + private List sshKeyPairNames; + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, description = "destination Host ID to deploy the VM to - parameter available for root admin only") private Long hostId; @@ -444,8 +448,15 @@ public String getName() { return name; } - public String getSSHKeyPairName() { - return sshKeyPairName; + public List getSSHKeyPairNames() { + List sshKeyPairs = new ArrayList(); + if(sshKeyPairNames != null) { + sshKeyPairs = sshKeyPairNames; + } + if(sshKeyPairName != null && !sshKeyPairName.isEmpty()) { + sshKeyPairs.add(sshKeyPairName); + } + return sshKeyPairs; } public Long getHostId() { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMSSHKeyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMSSHKeyCmd.java index ce481d8814c9..41509b0cf6be 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMSSHKeyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMSSHKeyCmd.java @@ -42,6 +42,9 @@ import com.cloud.uservm.UserVm; import com.cloud.vm.VirtualMachine; +import java.util.ArrayList; +import java.util.List; + @APICommand(name = "resetSSHKeyForVirtualMachine", responseObject = UserVmResponse.class, description = "Resets the SSH Key for virtual machine. " + "The virtual machine must be in a \"Stopped\" state. [async]", responseView = ResponseView.Restricted, entityType = {VirtualMachine.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = true) @@ -58,8 +61,12 @@ public class ResetVMSSHKeyCmd extends BaseAsyncCmd implements UserCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = UserVmResponse.class, required = true, description = "The ID of the virtual machine") private Long id; - @Parameter(name = ApiConstants.SSH_KEYPAIR, type = CommandType.STRING, required = true, description = "name of the ssh key pair used to login to the virtual machine") - private String name; + @Deprecated + @Parameter(name = ApiConstants.SSH_KEYPAIR, type = CommandType.STRING ,description = "name of the ssh key pair used to login to the virtual machine") + String name; + + @Parameter(name = ApiConstants.SSH_KEYPAIRS, type = CommandType.LIST, collectionType = CommandType.STRING, since="4.17", description = "names of the ssh key pairs to be used to login to the virtual machine") + List names; //Owner information @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "an optional account for the ssh key. Must be used with domainId.") @@ -78,8 +85,15 @@ public class ResetVMSSHKeyCmd extends BaseAsyncCmd implements UserCmd { /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// - public String getName() { - return name; + public List getNames() { + List keypairnames = new ArrayList(); + if (names != null) { + keypairnames = names; + } + if (name != null && !name.isEmpty()) { + keypairnames.add(name); + } + return keypairnames; } public Long getId() { diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java index 3483c17f4743..835c30c8ee9b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java @@ -280,9 +280,9 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co @Param(description = "List of read-only Vm details as comma separated string.", since = "4.16.0") private String readOnlyDetails; - @SerializedName(ApiConstants.SSH_KEYPAIR) - @Param(description = "ssh key-pair") - private String keyPairName; + @SerializedName(ApiConstants.SSH_KEYPAIRS) + @Param(description = "ssh key-pairs") + private String keyPairNames; @SerializedName("affinitygroup") @Param(description = "list of affinity groups associated with the virtual machine", responseObject = AffinityGroupResponse.class) @@ -588,8 +588,8 @@ public String getInstanceName() { return instanceName; } - public String getKeyPairName() { - return keyPairName; + public String getKeyPairNames() { + return keyPairNames; } public Set getAffinityGroupList() { @@ -848,8 +848,8 @@ public void setTags(Set tags) { this.tags = tags; } - public void setKeyPairName(String keyPairName) { - this.keyPairName = keyPairName; + public void setKeyPairNames(String keyPairNames) { + this.keyPairNames = keyPairNames; } public void setAffinityGroupList(Set affinityGroups) { diff --git a/engine/schema/src/main/java/com/cloud/user/dao/SSHKeyPairDao.java b/engine/schema/src/main/java/com/cloud/user/dao/SSHKeyPairDao.java index e035e9688c3e..b9941dc16f7c 100644 --- a/engine/schema/src/main/java/com/cloud/user/dao/SSHKeyPairDao.java +++ b/engine/schema/src/main/java/com/cloud/user/dao/SSHKeyPairDao.java @@ -37,4 +37,6 @@ public interface SSHKeyPairDao extends GenericDao { public SSHKeyPairVO findByPublicKey(long accountId, long domainId, String publicKey); + public List findByNames(long accountId, long domainId, List names); + } diff --git a/engine/schema/src/main/java/com/cloud/user/dao/SSHKeyPairDaoImpl.java b/engine/schema/src/main/java/com/cloud/user/dao/SSHKeyPairDaoImpl.java index 1a773ce926bf..6ad760063ca9 100644 --- a/engine/schema/src/main/java/com/cloud/user/dao/SSHKeyPairDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/user/dao/SSHKeyPairDaoImpl.java @@ -19,6 +19,7 @@ import java.util.List; +import com.cloud.utils.db.Filter; import org.springframework.stereotype.Component; import com.cloud.user.SSHKeyPairVO; @@ -63,6 +64,16 @@ public SSHKeyPairVO findByName(long accountId, long domainId, String name) { return findOneBy(sc); } + @Override + public List findByNames(long accountId, long domainId, List names) { + SearchCriteria sc = createSearchCriteria(); + final Filter filter = new Filter(SSHKeyPairVO.class,"name",false, null, null); + sc.addAnd("accountId", SearchCriteria.Op.EQ, accountId); + sc.addAnd("domainId", SearchCriteria.Op.EQ, domainId); + sc.addAnd("name", SearchCriteria.Op.IN, names.toArray()); + return this.search(sc, filter); + } + @Override public SSHKeyPairVO findByPublicKey(String publicKey) { SearchCriteria sc = createSearchCriteria(); diff --git a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java index e0b37e476287..421dcf4a822a 100644 --- a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java @@ -488,8 +488,7 @@ public Map getDetails() { public void setDetail(String name, String value) { assert (details != null) : "Did you forget to load the details?"; - - details.put(name, value); + this.details.put(name, value); } public void setDetails(Map details) { diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql b/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql index 1eeb2087cebc..fc53758cc9a3 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql @@ -471,7 +471,7 @@ SELECT `user_ip_address`.`id` AS `public_ip_id`, `user_ip_address`.`uuid` AS `public_ip_uuid`, `user_ip_address`.`public_ip_address` AS `public_ip_address`, - `ssh_keypairs`.`keypair_name` AS `keypair_name`, + `ssh_details`.`value` AS `keypair_names`, `resource_tags`.`id` AS `tag_id`, `resource_tags`.`uuid` AS `tag_uuid`, `resource_tags`.`key` AS `tag_key`, @@ -495,7 +495,7 @@ SELECT `affinity_group`.`description` AS `affinity_group_description`, `vm_instance`.`dynamically_scalable` AS `dynamically_scalable` FROM - (((((((((((((((((((((((((((((((((`user_vm` + ((((((((((((((((((((((((((((((((`user_vm` JOIN `vm_instance` ON (((`vm_instance`.`id` = `user_vm`.`id`) AND ISNULL(`vm_instance`.`removed`)))) JOIN `account` ON ((`vm_instance`.`account_id` = `account`.`id`))) @@ -524,9 +524,7 @@ FROM AND ISNULL(`vpc`.`removed`)))) LEFT JOIN `user_ip_address` ON ((`user_ip_address`.`vm_id` = `vm_instance`.`id`))) LEFT JOIN `user_vm_details` `ssh_details` ON (((`ssh_details`.`vm_id` = `vm_instance`.`id`) - AND (`ssh_details`.`name` = 'SSH.PublicKey')))) - LEFT JOIN `ssh_keypairs` ON (((`ssh_keypairs`.`public_key` = `ssh_details`.`value`) - AND (`ssh_keypairs`.`account_id` = `account`.`id`)))) + AND (`ssh_details`.`name` = 'SSH.KeyPairNames')))) LEFT JOIN `resource_tags` ON (((`resource_tags`.`resource_id` = `vm_instance`.`id`) AND (`resource_tags`.`resource_type` = 'UserVm')))) LEFT JOIN `async_job` ON (((`async_job`.`instance_id` = `vm_instance`.`id`) @@ -646,3 +644,11 @@ CREATE VIEW `cloud`.`domain_router_view` AS INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) SELECT UUID(), 3, 'listConfigurations', 'ALLOW', (SELECT MAX(`sort_order`)+1 FROM `cloud`.`role_permissions`) ON DUPLICATE KEY UPDATE rule=rule; INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) SELECT UUID(), 3, 'updateConfiguration', 'ALLOW', (SELECT MAX(`sort_order`)+1 FROM `cloud`.`role_permissions`) ON DUPLICATE KEY UPDATE rule=rule; + +INSERT INTO `cloud`.`user_vm_details`(`vm_id`, `name`, `value`) + SELECT `user_vm_details`.`vm_id`, 'SSH.KeyPairNames', `ssh_keypairs`.`keypair_name` + FROM `cloud`.`user_vm_details` + INNER JOIN `cloud`.`ssh_keypairs` ON ssh_keypairs.public_key = user_vm_details.value + INNER JOIN `cloud`.`vm_instance` ON vm_instance.id = user_vm_details.vm_id + WHERE ssh_keypairs.account_id = vm_instance.account_id; + diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java index dd5adf6e0bfa..1c147e288f4e 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java @@ -376,9 +376,13 @@ protected UserVm createKubernetesNode(String joinIp) throws ManagementServerExce logAndThrow(Level.ERROR, "Failed to read Kubernetes node configuration file", e); } String base64UserData = Base64.encodeBase64String(k8sNodeConfig.getBytes(com.cloud.utils.StringUtils.getPreferredCharset())); + List keypairs = new ArrayList(); + if (StringUtils.isNotBlank(kubernetesCluster.getKeyPair())) { + keypairs.add(kubernetesCluster.getKeyPair()); + } nodeVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, clusterTemplate, networkIds, owner, hostName, hostName, null, null, null, - Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, kubernetesCluster.getKeyPair(), + Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, keypairs, null, addrs, null, null, null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null); if (LOGGER.isInfoEnabled()) { LOGGER.info(String.format("Created node VM : %s, %s in the Kubernetes cluster : %s", hostName, nodeVm.getUuid(), kubernetesCluster.getName())); diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java index 938afaf76baa..57daa6dcf340 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterStartWorker.java @@ -213,9 +213,13 @@ private UserVm createKubernetesControlNode(final Network network, String serverI logAndThrow(Level.ERROR, "Failed to read Kubernetes control node configuration file", e); } String base64UserData = Base64.encodeBase64String(k8sControlNodeConfig.getBytes(com.cloud.utils.StringUtils.getPreferredCharset())); + List keypairs = new ArrayList(); + if (StringUtils.isNotBlank(kubernetesCluster.getKeyPair())) { + keypairs.add(kubernetesCluster.getKeyPair()); + } controlVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, clusterTemplate, networkIds, owner, hostName, hostName, null, null, null, - Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, kubernetesCluster.getKeyPair(), + Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, keypairs, requestedIps, addrs, null, null, null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null); if (LOGGER.isInfoEnabled()) { LOGGER.info(String.format("Created control VM ID: %s, %s in the Kubernetes cluster : %s", controlVm.getUuid(), hostName, kubernetesCluster.getName())); @@ -273,9 +277,13 @@ private UserVm createKubernetesAdditionalControlNode(final String joinIp, final logAndThrow(Level.ERROR, "Failed to read Kubernetes control configuration file", e); } String base64UserData = Base64.encodeBase64String(k8sControlNodeConfig.getBytes(com.cloud.utils.StringUtils.getPreferredCharset())); + List keypairs = new ArrayList(); + if (StringUtils.isNotBlank(kubernetesCluster.getKeyPair())) { + keypairs.add(kubernetesCluster.getKeyPair()); + } additionalControlVm = userVmService.createAdvancedVirtualMachine(zone, serviceOffering, clusterTemplate, networkIds, owner, hostName, hostName, null, null, null, - Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, kubernetesCluster.getKeyPair(), + Hypervisor.HypervisorType.None, BaseCmd.HTTPMethod.POST, base64UserData, keypairs, null, addrs, null, null, null, customParameterMap, null, null, null, null, true, UserVmManager.CKS_NODE, null); if (LOGGER.isInfoEnabled()) { LOGGER.info(String.format("Created control VM ID : %s, %s in the Kubernetes cluster : %s", additionalControlVm.getUuid(), hostName, kubernetesCluster.getName())); 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 75ad4b07c289..9cae5cc076e7 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -1072,7 +1072,7 @@ private Pair, Integer> searchForUserVMsInternal(ListVMsCmd cm } if (keyPairName != null) { - sb.and("keyPairName", sb.entity().getKeypairName(), SearchCriteria.Op.EQ); + sb.and("keyPairName", sb.entity().getKeypairNames(), SearchCriteria.Op.FIND_IN_SET); } if (!isRootAdmin) { diff --git a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java index ed52b03ce9c4..9eee2d62d777 100644 --- a/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/UserVmJoinDaoImpl.java @@ -219,7 +219,7 @@ public UserVmResponse newUserVmResponse(ResponseView view, String objectName, Us userVmResponse.setPublicIpId(userVm.getPublicIpUuid()); userVmResponse.setPublicIp(userVm.getPublicIpAddress()); - userVmResponse.setKeyPairName(userVm.getKeypairName()); + userVmResponse.setKeyPairNames(userVm.getKeypairNames()); userVmResponse.setOsTypeId(userVm.getGuestOsUuid()); GuestOS guestOS = ApiDBUtils.findGuestOSById(userVm.getGuestOsId()); if (guestOS != null) { diff --git a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java index 5f28119a5db7..43d6b58a488c 100644 --- a/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/UserVmJoinVO.java @@ -353,8 +353,8 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro @Column(name = "project_name") private String projectName; - @Column(name = "keypair_name") - private String keypairName; + @Column(name = "keypair_names") + private String keypairNames; @Column(name = "job_id") private Long jobId; @@ -779,8 +779,8 @@ public String getProjectName() { return projectName; } - public String getKeypairName() { - return keypairName; + public String getKeypairNames() { + return keypairNames; } public boolean isLimitCpuUse() { diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 8b266b948171..e0c1c72a6e5b 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -313,7 +313,6 @@ import com.cloud.user.AccountManager; import com.cloud.user.AccountService; import com.cloud.user.ResourceLimitService; -import com.cloud.user.SSHKeyPair; import com.cloud.user.SSHKeyPairVO; import com.cloud.user.User; import com.cloud.user.UserStatisticsVO; @@ -888,8 +887,8 @@ public UserVm resetVMSSHKey(ResetVMSSHKeyCmd cmd) throws ResourceUnavailableExce Account caller = CallContext.current().getCallingAccount(); Account owner = _accountMgr.finalizeOwner(caller, cmd.getAccountName(), cmd.getDomainId(), cmd.getProjectId()); Long vmId = cmd.getId(); - UserVmVO userVm = _vmDao.findById(cmd.getId()); + if (userVm == null) { throw new InvalidParameterValueException("unable to find a virtual machine by id" + cmd.getId()); } @@ -907,18 +906,27 @@ public UserVm resetVMSSHKey(ResetVMSSHKeyCmd cmd) throws ResourceUnavailableExce throw new InvalidParameterValueException("Vm " + userVm + " should be stopped to do SSH Key reset"); } - SSHKeyPairVO s = _sshKeyPairDao.findByName(owner.getAccountId(), owner.getDomainId(), cmd.getName()); - if (s == null) { - throw new InvalidParameterValueException("A key pair with name '" + cmd.getName() + "' does not exist for account " + owner.getAccountName() - + " in specified domain id"); + if (cmd.getNames() == null || cmd.getNames().isEmpty()) { + throw new InvalidParameterValueException("'keypair' or 'keyparis' must be specified"); } - _accountMgr.checkAccess(caller, null, true, userVm); + String keypairnames = ""; + String sshPublicKeys = ""; + List pairs = new ArrayList<>(); - String sshPublicKey = s.getPublicKey(); + pairs = _sshKeyPairDao.findByNames(owner.getAccountId(), owner.getDomainId(), cmd.getNames()); + if (pairs == null || pairs.size() != cmd.getNames().size()) { + throw new InvalidParameterValueException("Not all specified keyparis exist"); + } + sshPublicKeys = pairs.stream().map(p -> p.getPublicKey()).collect(Collectors.joining("\n")); + keypairnames = String.join(",", cmd.getNames()); - boolean result = resetVMSSHKeyInternal(vmId, sshPublicKey); + _accountMgr.checkAccess(caller, null, true, userVm); + + boolean result = resetVMSSHKeyInternal(vmId, sshPublicKeys, keypairnames); + UserVmVO vm = _vmDao.findById(vmId); + _vmDao.loadDetails(vm); if (!result) { throw new CloudRuntimeException("Failed to reset SSH Key for the virtual machine "); } @@ -933,7 +941,7 @@ protected void removeEncryptedPasswordFromUserVmVoDetails(long vmId) { userVmDetailsDao.removeDetail(vmId, VmDetailConstants.ENCRYPTED_PASSWORD); } - private boolean resetVMSSHKeyInternal(Long vmId, String sshPublicKey) throws ResourceUnavailableException, InsufficientCapacityException { + private boolean resetVMSSHKeyInternal(Long vmId, String sshPublicKeys, String keypairnames) throws ResourceUnavailableException, InsufficientCapacityException { Long userId = CallContext.current().getCallingUserId(); VMInstanceVO vmInstance = _vmDao.findById(vmId); @@ -954,8 +962,7 @@ private boolean resetVMSSHKeyInternal(Long vmId, String sshPublicKey) throws Res if (element == null) { throw new CloudRuntimeException("Can't find network element for " + Service.UserData.getName() + " provider needed for SSH Key reset"); } - boolean result = element.saveSSHKey(defaultNetwork, defaultNicProfile, vmProfile, sshPublicKey); - + boolean result = element.saveSSHKey(defaultNetwork, defaultNicProfile, vmProfile, sshPublicKeys); // Need to reboot the virtual machine so that the password gets redownloaded from the DomR, and reset on the VM if (!result) { s_logger.debug("Failed to reset SSH Key for the virtual machine; no need to reboot the vm"); @@ -963,7 +970,8 @@ private boolean resetVMSSHKeyInternal(Long vmId, String sshPublicKey) throws Res } else { final UserVmVO userVm = _vmDao.findById(vmId); _vmDao.loadDetails(userVm); - userVm.setDetail(VmDetailConstants.SSH_PUBLIC_KEY, sshPublicKey); + userVm.setDetail(VmDetailConstants.SSH_PUBLIC_KEY, sshPublicKeys); + userVm.setDetail(VmDetailConstants.SSH_KEY_PAIR_NAMES, keypairnames); _vmDao.saveDetails(userVm); if (vmInstance.getState() == State.Stopped) { @@ -3403,7 +3411,7 @@ private boolean validPassword(String password) { @ActionEvent(eventType = EventTypes.EVENT_VM_CREATE, eventDescription = "deploying Vm", create = true) public UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, - String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, + String userData, List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParametes, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { @@ -3453,7 +3461,7 @@ public UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOff } return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, - userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParametes, customId, dhcpOptionMap, + userData, sshKeyPairs, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParametes, customId, dhcpOptionMap, dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, null, overrideDiskOfferingId); } @@ -3462,7 +3470,7 @@ public UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOff @ActionEvent(eventType = EventTypes.EVENT_VM_CREATE, eventDescription = "deploying Vm", create = true) public UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, - HTTPMethod httpmethod, String userData, String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, + HTTPMethod httpmethod, String userData, List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { @@ -3564,7 +3572,7 @@ public UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, Service } return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, securityGroupIdList, group, httpmethod, - userData, sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap, dataDiskTemplateToDiskOfferingMap, + userData, sshKeyPairs, hypervisor, caller, requestedIps, defaultIps, displayVm, keyboard, affinityGroupIdList, customParameters, customId, dhcpOptionMap, dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, null, overrideDiskOfferingId); } @@ -3572,7 +3580,7 @@ public UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, Service @ActionEvent(eventType = EventTypes.EVENT_VM_CREATE, eventDescription = "deploying Vm", create = true) public UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, - String sshKeyPair, Map requestedIps, IpAddresses defaultIps, Boolean displayvm, String keyboard, List affinityGroupIdList, + List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayvm, String keyboard, List affinityGroupIdList, Map customParametrs, String customId, Map> dhcpOptionsMap, Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String type, Long overrideDiskOfferingId) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException { @@ -3623,9 +3631,8 @@ public UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serv } } verifyExtraDhcpOptionsNetwork(dhcpOptionsMap, networkList); - return createVirtualMachine(zone, serviceOffering, template, hostName, displayName, owner, diskOfferingId, diskSize, networkList, null, group, httpmethod, userData, - sshKeyPair, hypervisor, caller, requestedIps, defaultIps, displayvm, keyboard, affinityGroupIdList, customParametrs, customId, dhcpOptionsMap, + sshKeyPairs, hypervisor, caller, requestedIps, defaultIps, displayvm, keyboard, affinityGroupIdList, customParametrs, customId, dhcpOptionsMap, dataDiskTemplateToDiskOfferingMap, userVmOVFPropertiesMap, dynamicScalingEnabled, type, overrideDiskOfferingId); } @@ -3742,7 +3749,7 @@ public void checkNameForRFCCompliance(String name) { @DB private UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate tmplt, String hostName, String displayName, Account owner, Long diskOfferingId, Long diskSize, List networkList, List securityGroupIdList, String group, HTTPMethod httpmethod, String userData, - String sshKeyPair, HypervisorType hypervisor, Account caller, Map requestedIps, IpAddresses defaultIps, Boolean isDisplayVm, String keyboard, + List sshKeyPairs, HypervisorType hypervisor, Account caller, Map requestedIps, IpAddresses defaultIps, Boolean isDisplayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, Map datadiskTemplateToDiskOfferringMap, Map userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String type, Long overrideDiskOfferingId) throws InsufficientCapacityException, ResourceUnavailableException, @@ -3945,14 +3952,16 @@ private UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffe // Find an SSH public key corresponding to the key pair name, if one is // given - String sshPublicKey = null; - if (sshKeyPair != null && !sshKeyPair.equals("")) { - SSHKeyPair pair = _sshKeyPairDao.findByName(owner.getAccountId(), owner.getDomainId(), sshKeyPair); - if (pair == null) { - throw new InvalidParameterValueException("A key pair with name '" + sshKeyPair + "' was not found."); + String sshPublicKeys = ""; + String keypairnames = ""; + if (!sshKeyPairs.isEmpty()) { + List pairs = _sshKeyPairDao.findByNames(owner.getAccountId(), owner.getDomainId(), sshKeyPairs); + if (pairs == null || pairs.size() != sshKeyPairs.size()) { + throw new InvalidParameterValueException("Not all specified keyparis exist"); } - sshPublicKey = pair.getPublicKey(); + sshPublicKeys = pairs.stream().map(p -> p.getPublicKey()).collect(Collectors.joining("\n")); + keypairnames = String.join(",", sshKeyPairs); } LinkedHashMap> networkNicMap = new LinkedHashMap<>(); @@ -4014,7 +4023,7 @@ private UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffe throw new InvalidParameterValueException("Unable to deploy VM as UserData is provided while deploying the VM, but there is no support for " + Network.Service.UserData.getName() + " service in the default network " + network.getId()); } - if ((sshPublicKey != null) && (!sshPublicKey.isEmpty())) { + if ((sshPublicKeys != null) && (!sshPublicKeys.isEmpty())) { throw new InvalidParameterValueException("Unable to deploy VM as SSH keypair is provided while deploying the VM, but there is no support for " + Network.Service.UserData.getName() + " service in the default network " + network.getId()); } @@ -4113,8 +4122,8 @@ private UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOffe dynamicScalingEnabled = dynamicScalingEnabled && checkIfDynamicScalingCanBeEnabled(null, offering, template, zone.getId()); UserVmVO vm = commitUserVm(zone, template, hostName, displayName, owner, diskOfferingId, diskSize, userData, caller, isDisplayVm, keyboard, accountId, userId, offering, - isIso, sshPublicKey, networkNicMap, id, instanceName, uuidName, hypervisorType, customParameters, dhcpOptionMap, - datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, type, rootDiskOfferingId); + isIso, sshPublicKeys, networkNicMap, id, instanceName, uuidName, hypervisorType, customParameters, dhcpOptionMap, + datadiskTemplateToDiskOfferringMap, userVmOVFPropertiesMap, dynamicScalingEnabled, type, rootDiskOfferingId, keypairnames); // Assign instance to the group try { @@ -4248,10 +4257,10 @@ private String generateHostName(String uuidName) { private UserVmVO commitUserVm(final boolean isImport, final DataCenter zone, final Host host, final Host lastHost, final VirtualMachineTemplate template, final String hostName, final String displayName, final Account owner, final Long diskOfferingId, final Long diskSize, final String userData, final Account caller, final Boolean isDisplayVm, final String keyboard, - final long accountId, final long userId, final ServiceOffering offering, final boolean isIso, final String sshPublicKey, final LinkedHashMap> networkNicMap, + final long accountId, final long userId, final ServiceOffering offering, final boolean isIso, final String sshPublicKeys, final LinkedHashMap> networkNicMap, final long id, final String instanceName, final String uuidName, final HypervisorType hypervisorType, final Map customParameters, final Map> extraDhcpOptionMap, final Map dataDiskTemplateToDiskOfferingMap, - final Map userVmOVFPropertiesMap, final VirtualMachine.PowerState powerState, final boolean dynamicScalingEnabled, String type, final Long rootDiskOfferingId) throws InsufficientCapacityException { + final Map userVmOVFPropertiesMap, final VirtualMachine.PowerState powerState, final boolean dynamicScalingEnabled, String type, final Long rootDiskOfferingId, String sshkeypairs) throws InsufficientCapacityException { return Transaction.execute(new TransactionCallbackWithException() { @Override public UserVmVO doInTransaction(TransactionStatus status) throws InsufficientCapacityException { @@ -4265,8 +4274,12 @@ public UserVmVO doInTransaction(TransactionStatus status) throws InsufficientCap vm.details.putAll(details); } - if (sshPublicKey != null) { - vm.setDetail(VmDetailConstants.SSH_PUBLIC_KEY, sshPublicKey); + if (sshPublicKeys != "") { + vm.setDetail(VmDetailConstants.SSH_PUBLIC_KEY, sshPublicKeys); + } + + if (sshkeypairs != "") { + vm.setDetail(VmDetailConstants.SSH_KEY_PAIR_NAMES, sshkeypairs); } if (keyboard != null && !keyboard.isEmpty()) { @@ -4452,16 +4465,16 @@ private void persistVMDeployAsIsProperties(UserVmVO vm, Map user private UserVmVO commitUserVm(final DataCenter zone, final VirtualMachineTemplate template, final String hostName, final String displayName, final Account owner, final Long diskOfferingId, final Long diskSize, final String userData, final Account caller, final Boolean isDisplayVm, final String keyboard, - final long accountId, final long userId, final ServiceOfferingVO offering, final boolean isIso, final String sshPublicKey, final LinkedHashMap> networkNicMap, + final long accountId, final long userId, final ServiceOfferingVO offering, final boolean isIso, final String sshPublicKeys, final LinkedHashMap> networkNicMap, final long id, final String instanceName, final String uuidName, final HypervisorType hypervisorType, final Map customParameters, final Map> extraDhcpOptionMap, final Map dataDiskTemplateToDiskOfferingMap, - Map userVmOVFPropertiesMap, final boolean dynamicScalingEnabled, String type, final Long rootDiskOfferingId) throws InsufficientCapacityException { + Map userVmOVFPropertiesMap, final boolean dynamicScalingEnabled, String type, final Long rootDiskOfferingId, String sshkeypairs) throws InsufficientCapacityException { return commitUserVm(false, zone, null, null, template, hostName, displayName, owner, diskOfferingId, diskSize, userData, caller, isDisplayVm, keyboard, - accountId, userId, offering, isIso, sshPublicKey, networkNicMap, + accountId, userId, offering, isIso, sshPublicKeys, networkNicMap, id, instanceName, uuidName, hypervisorType, customParameters, extraDhcpOptionMap, dataDiskTemplateToDiskOfferingMap, - userVmOVFPropertiesMap, null, dynamicScalingEnabled, type, rootDiskOfferingId); + userVmOVFPropertiesMap, null, dynamicScalingEnabled, type, rootDiskOfferingId, sshkeypairs); } public void validateRootDiskResize(final HypervisorType hypervisorType, Long rootDiskSize, VMTemplateVO templateVO, UserVmVO vm, final Map customParameters) throws InvalidParameterValueException @@ -5689,7 +5702,7 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE Long size = cmd.getSize(); String group = cmd.getGroup(); String userData = cmd.getUserData(); - String sshKeyPairName = cmd.getSSHKeyPairName(); + List sshKeyPairNames = cmd.getSSHKeyPairNames(); Boolean displayVm = cmd.isDisplayVm(); String keyboard = cmd.getKeyboard(); Map dataDiskTemplateToDiskOfferingMap = cmd.getDataDiskTemplateToDiskOfferingMap(); @@ -5699,14 +5712,14 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE throw new InvalidParameterValueException("Can't specify network Ids in Basic zone"); } else { vm = createBasicSecurityGroupVirtualMachine(zone, serviceOffering, template, getSecurityGroupIdList(cmd), owner, name, displayName, diskOfferingId, - size , group , cmd.getHypervisor(), cmd.getHttpMethod(), userData , sshKeyPairName , cmd.getIpToNetworkMap(), addrs, displayVm , keyboard , cmd.getAffinityGroupIdList(), + size , group , cmd.getHypervisor(), cmd.getHttpMethod(), userData, sshKeyPairNames, cmd.getIpToNetworkMap(), addrs, displayVm , keyboard , cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, overrideDiskOfferingId); } } else { if (zone.isSecurityGroupEnabled()) { vm = createAdvancedSecurityGroupVirtualMachine(zone, serviceOffering, template, networkIds, getSecurityGroupIdList(cmd), owner, name, - displayName, diskOfferingId, size, group, cmd.getHypervisor(), cmd.getHttpMethod(), userData, sshKeyPairName, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard, + displayName, diskOfferingId, size, group, cmd.getHypervisor(), cmd.getHttpMethod(), userData, sshKeyPairNames, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard, cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, overrideDiskOfferingId); @@ -5715,10 +5728,11 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE throw new InvalidParameterValueException("Can't create vm with security groups; security group feature is not enabled per zone"); } vm = createAdvancedVirtualMachine(zone, serviceOffering, template, networkIds, owner, name, displayName, diskOfferingId, size, group, - cmd.getHypervisor(), cmd.getHttpMethod(), userData, sshKeyPairName, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard, cmd.getAffinityGroupIdList(), cmd.getDetails(), + cmd.getHypervisor(), cmd.getHttpMethod(), userData, sshKeyPairNames, cmd.getIpToNetworkMap(), addrs, displayVm, keyboard, cmd.getAffinityGroupIdList(), cmd.getDetails(), cmd.getCustomId(), cmd.getDhcpOptionsMap(), dataDiskTemplateToDiskOfferingMap, userVmOVFProperties, dynamicScalingEnabled, null, overrideDiskOfferingId); } } + // check if this templateId has a child ISO List child_templates = _templateDao.listByParentTemplatetId(templateId); for (VMTemplateVO tmpl: child_templates){ @@ -5750,7 +5764,6 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE } } } - return vm; } @@ -7694,13 +7707,13 @@ public void finalizeUnmanage(VirtualMachine vm) { } private void encryptAndStorePassword(UserVmVO vm, String password) { - String sshPublicKey = vm.getDetail(VmDetailConstants.SSH_PUBLIC_KEY); - if (sshPublicKey != null && !sshPublicKey.equals("") && password != null && !password.equals("saved_password")) { - if (!sshPublicKey.startsWith("ssh-rsa")) { + String sshPublicKeys = vm.getDetail(VmDetailConstants.SSH_PUBLIC_KEY); + if (sshPublicKeys != null && !sshPublicKeys.equals("") && password != null && !password.equals("saved_password")) { + if (!sshPublicKeys.startsWith("ssh-rsa")) { s_logger.warn("Only RSA public keys can be used to encrypt a vm password."); return; } - String encryptedPasswd = RSAHelper.encryptWithSSHPublicKey(sshPublicKey, password); + String encryptedPasswd = RSAHelper.encryptWithSSHPublicKey(sshPublicKeys, password); if (encryptedPasswd == null) { throw new CloudRuntimeException("Error encrypting password"); } @@ -7830,7 +7843,7 @@ private void deleteVolumesFromVm(List volumes, boolean expunge) { @Override public UserVm importVM(final DataCenter zone, final Host host, final VirtualMachineTemplate template, final String instanceName, final String displayName, final Account owner, final String userData, final Account caller, final Boolean isDisplayVm, final String keyboard, - final long accountId, final long userId, final ServiceOffering serviceOffering, final String sshPublicKey, + final long accountId, final long userId, final ServiceOffering serviceOffering, final String sshPublicKeys, final String hostName, final HypervisorType hypervisorType, final Map customParameters, final VirtualMachine.PowerState powerState) throws InsufficientCapacityException { if (zone == null) { throw new InvalidParameterValueException("Unable to import virtual machine with invalid zone"); @@ -7851,9 +7864,9 @@ public UserVm importVM(final DataCenter zone, final Host host, final VirtualMach final Boolean dynamicScalingEnabled = checkIfDynamicScalingCanBeEnabled(null, serviceOffering, template, zone.getId()); return commitUserVm(true, zone, host, lastHost, template, hostName, displayName, owner, null, null, userData, caller, isDisplayVm, keyboard, - accountId, userId, serviceOffering, template.getFormat().equals(ImageFormat.ISO), sshPublicKey, null, + accountId, userId, serviceOffering, template.getFormat().equals(ImageFormat.ISO), sshPublicKeys, null, id, instanceName, uuidName, hypervisorType, customParameters, - null, null, null, powerState, dynamicScalingEnabled, null, serviceOffering.getDiskOfferingId()); + null, null, null, powerState, dynamicScalingEnabled, null, serviceOffering.getDiskOfferingId(), null); } @Override diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 02b0648abe7e..cda138201320 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -1240,6 +1240,7 @@ "label.keyboard": "Keyboard language", "label.keyboardtype": "Keyboard type", "label.keypair": "SSH Key Pair", +"label.keypairs": "SSH Key Pair(s)", "label.kubeconfig.cluster": "Kubernetes Cluster Config", "label.kubernetes": "Kubernetes", "label.kubernetes.access.details": "The kubernetes nodes can be accessed via ssh using:
ssh -i [ssh_key] -p [port_number] cloud@[public_ip_address]

where,
ssh_key: points to the ssh private key file corresponding to the key that was associated while creating the Kubernetes cluster. If no ssh key was provided during Kubernetes cluster creation, use the ssh private key of the management server.
port_number: can be obtained from the Port Forwarding Tab (Public Port column)", diff --git a/ui/src/components/view/InfoCard.vue b/ui/src/components/view/InfoCard.vue index 6b61dad2c138..fc36154dcb2c 100644 --- a/ui/src/components/view/InfoCard.vue +++ b/ui/src/components/view/InfoCard.vue @@ -361,11 +361,13 @@ {{ resource.group || resource.groupid }} -
-
{{ $t('label.keypair') }}
+
+
{{ $t('label.keypairs') }}
- {{ resource.keypair }} +
  • + {{ keypair }} +
  • @@ -813,6 +815,15 @@ export default { return null }, + keypairs () { + if (!this.resource.keypairs) { + return null + } + if (typeof this.resource.keypairs === 'string' || this.resource.keypairs instanceof String) { + return this.resource.keypairs.split(',') + } + return [this.resource.keypairs.toString()] + }, templateIcon () { return this.resource.templateid }, diff --git a/ui/src/config/section/compute.js b/ui/src/config/section/compute.js index f1f23405a08b..178ac9c063f3 100644 --- a/ui/src/config/section/compute.js +++ b/ui/src/config/section/compute.js @@ -343,31 +343,9 @@ export default { message: 'message.desc.reset.ssh.key.pair', docHelp: 'adminguide/virtual_machines.html#resetting-ssh-keys', dataView: true, - args: ['keypair', 'account', 'domainid'], show: (record) => { return ['Stopped'].includes(record.state) }, - mapping: { - keypair: { - api: 'listSSHKeyPairs', - params: (record) => { return { account: record.account, domainid: record.domainid } } - }, - account: { - value: (record) => { return record.account } - }, - domainid: { - value: (record) => { return record.domainid } - } - }, - successMethod: (obj, result) => { - const vm = result.jobresult.virtualmachine || {} - if (result.jobstatus === 1 && vm.password) { - const name = vm.displayname || vm.name || vm.id - obj.$notification.success({ - message: `${obj.$t('label.reset.ssh.key.pair.on.vm')}: ` + name, - description: `${obj.$t('label.password.reset.confirm')}: ` + vm.password, - duration: 0 - }) - } - } + popup: true, + component: () => import('@/views/compute/ResetSshKeyPair') }, { api: 'assignVirtualMachine', diff --git a/ui/src/views/compute/DeployVM.vue b/ui/src/views/compute/DeployVM.vue index 211710bce658..964364c02301 100644 --- a/ui/src/views/compute/DeployVM.vue +++ b/ui/src/views/compute/DeployVM.vue @@ -440,7 +440,7 @@ :items="options.sshKeyPairs" :row-count="rowCount.sshKeyPairs" :zoneId="zoneId" - :value="sshKeyPair ? sshKeyPair.name : ''" + :value="sshKeyPairs" :loading="loading.sshKeyPairs" :preFillContent="dataPreFill" @select-ssh-key-pair-item="($event) => updateSshKeyPairs($event)" @@ -797,7 +797,7 @@ export default { templateid: null, templatename: null, keyboard: null, - keypair: null, + keypairs: [], group: null, affinitygroupids: [], affinitygroup: [], @@ -861,6 +861,7 @@ export default { networks: [], networksAdd: [], zone: {}, + sshKeyPairs: [], sshKeyPair: {}, overrideDiskOffering: {}, templateFilter: [ @@ -1186,7 +1187,6 @@ export default { if (this.rootDiskSelected?.id) { instanceConfig.overridediskofferingid = this.rootDiskSelected.id } - console.log('overrided value ' + instanceConfig.overridediskofferingid) if (instanceConfig.overridediskofferingid) { this.overrideDiskOffering = _.find(this.options.diskOfferings, (option) => option.id === instanceConfig.overridediskofferingid) } else { @@ -1195,7 +1195,6 @@ export default { this.zone = _.find(this.options.zones, (option) => option.id === instanceConfig.zoneid) this.affinityGroups = _.filter(this.options.affinityGroups, (option) => _.includes(instanceConfig.affinitygroupids, option.id)) this.networks = _.filter(this.options.networks, (option) => _.includes(instanceConfig.networkids, option.id)) - this.sshKeyPair = _.find(this.options.sshKeyPairs, (option) => option.name === instanceConfig.keypair) if (this.zone) { this.vm.zoneid = this.zone.id @@ -1276,6 +1275,9 @@ export default { if (this.affinityGroups) { this.vm.affinitygroup = this.affinityGroups } + if (this.sshKeyPairs) { + this.vm.keypairs = this.sshKeyPairs + } } }, serviceOffering (oldValue, newValue) { @@ -1314,7 +1316,7 @@ export default { this.form.getFieldDecorator('affinitygroupids', { initialValue: [], preserve: true }) this.form.getFieldDecorator('networkids', { initialValue: [], preserve: true }) this.form.getFieldDecorator('defaultnetworkid', { initialValue: undefined, preserve: true }) - this.form.getFieldDecorator('keypair', { initialValue: undefined, preserve: true }) + this.form.getFieldDecorator('keypairs', { initialValue: [], preserve: true }) this.form.getFieldDecorator('cpunumber', { initialValue: undefined, preserve: true }) this.form.getFieldDecorator('cpuSpeed', { initialValue: undefined, preserve: true }) this.form.getFieldDecorator('memory', { initialValue: undefined, preserve: true }) @@ -1615,16 +1617,11 @@ export default { updateNetworkConfig (networks) { this.networkConfig = networks }, - updateSshKeyPairs (name) { - if (name === this.$t('label.noselect')) { - this.form.setFieldsValue({ - keypair: undefined - }) - return - } + updateSshKeyPairs (names) { this.form.setFieldsValue({ - keypair: name + keypairs: names }) + this.sshKeyPairs = names.map((sshKeyPair) => { return sshKeyPair.name }) }, escapePropertyKey (key) { return key.split('.').join('\\002E') @@ -1831,7 +1828,8 @@ export default { deployVmData.securitygroupids = this.securitygroupids.join(',') } // step 7: select ssh key pair - deployVmData.keypair = values.keypair + deployVmData.keypairs = this.sshKeyPairs.join(',') + if (values.name) { deployVmData.name = values.name deployVmData.displayname = values.name diff --git a/ui/src/views/compute/ResetSshKeyPair.vue b/ui/src/views/compute/ResetSshKeyPair.vue new file mode 100644 index 000000000000..e09745a695c1 --- /dev/null +++ b/ui/src/views/compute/ResetSshKeyPair.vue @@ -0,0 +1,178 @@ +// 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. + + + + + + diff --git a/ui/src/views/compute/wizard/SshKeyPairSelection.vue b/ui/src/views/compute/wizard/SshKeyPairSelection.vue index 222868e0e169..79b0d1c9ba14 100644 --- a/ui/src/views/compute/wizard/SshKeyPairSelection.vue +++ b/ui/src/views/compute/wizard/SshKeyPairSelection.vue @@ -25,15 +25,16 @@ + :scroll="{ y: 225 }" > + +
    0 }, value: { - type: String, - default: '' + type: Array, + default: () => [] }, loading: { type: Boolean, @@ -103,8 +104,7 @@ export default { width: '30%' } ], - selectedRowKeys: [this.$t('label.noselect')], - dataItems: [], + selectedRowKeys: [], oldZoneId: null, options: { page: 1, @@ -114,64 +114,38 @@ export default { } }, computed: { - tableSource () { - const dataItems = [] - - if (this.options.page === 1) { - dataItems.push({ - key: this.$t('label.noselect'), - name: this.$t('label.noselect'), - account: '-', - domain: '-' - }) - } - - this.items.map((item) => { - dataItems.push({ - key: item.name, - name: item.name, - account: item.account, - domain: item.domain - }) - }) - - return dataItems - }, rowSelection () { return { - type: 'radio', - selectedRowKeys: this.selectedRowKeys, - onChange: this.onSelectRow + type: 'checkbox', + onChange: (selectedRowKeys, selectedRows) => { + this.$emit('select-ssh-key-pair-item', selectedRows) + } } } }, watch: { value (newValue, oldValue) { if (newValue && newValue !== oldValue) { - this.selectedRowKeys = [newValue] + this.selectedRowKeys = newValue } }, loading () { if (!this.loading) { - if (this.preFillContent.keypair) { - this.selectedRowKeys = [this.preFillContent.keypair] - this.$emit('select-ssh-key-pair-item', this.preFillContent.keypair) + if (this.preFillContent.keypairs) { + this.selectedRowKeys = this.preFillContent.keypairs + this.$emit('select-ssh-key-pair-item', this.selectedRowKeys) } else { if (this.oldZoneId === this.zoneId) { return } this.oldZoneId = this.zoneId - this.selectedRowKeys = [this.$t('label.noselect')] - this.$emit('select-ssh-key-pair-item', this.$t('label.noselect')) + this.selectedRowKeys = [] + this.$emit('select-ssh-key-pair-item', this.selectedRowKeys) } } } }, methods: { - onSelectRow (value) { - this.selectedRowKeys = value - this.$emit('select-ssh-key-pair-item', value[0]) - }, handleSearch (value) { this.filter = value this.options.page = 1 @@ -188,16 +162,6 @@ export default { this.options.page = page this.options.pageSize = pageSize this.$emit('handle-search-filter', this.options) - }, - onClickRow (record) { - return { - on: { - click: () => { - this.selectedRowKeys = [record.key] - this.$emit('select-ssh-key-pair-item', record.key) - } - } - } } } }