This is an automated email from the ASF dual-hosted git repository.

nvazquez pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new 85c5997  Multiple SSH Keys support (#5965)
85c5997 is described below

commit 85c59979f76cef39559c75000de536a9103318bc
Author: David Jumani <[email protected]>
AuthorDate: Wed Mar 2 06:00:55 2022 +0530

    Multiple SSH Keys support (#5965)
    
    * keypairs added in api-constants
    
    * names parameter added
    
    * findbynames method added in dao
    
    * change in impl to find and reset multiple keys
    
    * findbynames method implemented
    
    * log the publickeys, check the ssh keys given exists or not
    
    * new ArrayList<>
    
    * SQL IN toArray
    
    * keypair
    
    * null pointer exception solved with + concatanation
    
    * null pointer exception solved with + concatanation
    
    * error resolved
    
    * keypair name to names in uservmresponse
    
    * keypair name is set in the uservmresponse, from the details
    
    * null checks are removed, keypairnames are stored in a string, sent to the 
resetvmsshinternal, and added in details
    
    * commit first eval
    
    * deploy vm takes multiple ssh-keys
    
    * Deploy VM UI changed to accept multiple ssh keys
    
    * Reset SSH UI API changed
    
    * ResetSSH.vue
    
    * ssh keys joined, ssh added in infocard
    
    * changes made
    
    * schema error resolved
    
    * potential null pointer exception removed
    
    * Update UserVmManagerImpl.java
    
    unnecessary check removed.
    
    * Update DeployVMCmd.java
    
    * Update DeployVMCmd.java
    
    * Update ResetVMSSHKeyCmd.java
    
    * Update UserVmJoinDaoImpl.java
    
    * .
    
    * arraylist
    
    * Update DeployVMCmd.java
    
    * Update UserVmManagerImpl.java
    
    * Update ResetVMSSHKeyCmd.java
    
    * Update db
    
    * Fix list vm by keypair
    
    * ui fixes
    
    * Fix typos
    
    * ui fixes
    
    * Cleanup
    
    * Adding deprecated and since in api params
    
    * Adding upgrade for existing vms with ssh keys
    
    * Handle no key for cks
    
    * Show existing keyparis in reset ssh key form
    
    * get keys from the right account
    
    Co-authored-by: bicrxm <[email protected]>
---
 api/src/main/java/com/cloud/vm/UserVmService.java  |   6 +-
 .../main/java/com/cloud/vm/VmDetailConstants.java  |   2 +
 .../org/apache/cloudstack/api/ApiConstants.java    |   1 +
 .../api/command/user/vm/DeployVMCmd.java           |  15 +-
 .../api/command/user/vm/ResetVMSSHKeyCmd.java      |  22 ++-
 .../cloudstack/api/response/UserVmResponse.java    |  14 +-
 .../java/com/cloud/user/dao/SSHKeyPairDao.java     |   2 +
 .../java/com/cloud/user/dao/SSHKeyPairDaoImpl.java |  11 ++
 .../src/main/java/com/cloud/vm/VMInstanceVO.java   |   3 +-
 .../resources/META-INF/db/schema-41610to41700.sql  |  16 +-
 ...ernetesClusterResourceModifierActionWorker.java |   6 +-
 .../KubernetesClusterStartWorker.java              |  12 +-
 .../java/com/cloud/api/query/QueryManagerImpl.java |   2 +-
 .../com/cloud/api/query/dao/UserVmJoinDaoImpl.java |   2 +-
 .../java/com/cloud/api/query/vo/UserVmJoinVO.java  |   8 +-
 .../main/java/com/cloud/vm/UserVmManagerImpl.java  | 113 +++++++------
 ui/public/locales/en.json                          |   1 +
 ui/src/components/view/InfoCard.vue                |  17 +-
 ui/src/config/section/compute.js                   |  26 +--
 ui/src/views/compute/DeployVM.vue                  |  26 ++-
 ui/src/views/compute/ResetSshKeyPair.vue           | 178 +++++++++++++++++++++
 .../views/compute/wizard/SshKeyPairSelection.vue   |  72 +++------
 22 files changed, 378 insertions(+), 177 deletions(-)

diff --git a/api/src/main/java/com/cloud/vm/UserVmService.java 
b/api/src/main/java/com/cloud/vm/UserVmService.java
index 8a1d615..2f6888a 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 @@ public interface UserVmService {
      */
     UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, 
ServiceOffering serviceOffering, VirtualMachineTemplate template, List<Long> 
securityGroupIdList,
         Account owner, String hostName, String displayName, Long 
diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, 
HTTPMethod httpmethod,
-        String userData, String sshKeyPair, Map<Long, IpAddresses> 
requestedIps, IpAddresses defaultIp, Boolean displayVm, String keyboard,
+        String userData, List<String> sshKeyPairs, Map<Long, IpAddresses> 
requestedIps, IpAddresses defaultIp, Boolean displayVm, String keyboard,
         List<Long> affinityGroupIdList, Map<String, String> customParameter, 
String customId, Map<String, Map<Integer, String>> dhcpOptionMap,
         Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap,
         Map<String, String> userVmOVFProperties, boolean 
dynamicScalingEnabled, Long overrideDiskOfferingId) throws 
InsufficientCapacityException,
@@ -297,7 +297,7 @@ public interface UserVmService {
      */
     UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, 
ServiceOffering serviceOffering, VirtualMachineTemplate template, List<Long> 
networkIdList,
         List<Long> securityGroupIdList, Account owner, String hostName, String 
displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType 
hypervisor,
-        HTTPMethod httpmethod, String userData, String sshKeyPair, Map<Long, 
IpAddresses> requestedIps, IpAddresses defaultIps, Boolean displayVm, String 
keyboard,
+        HTTPMethod httpmethod, String userData, List<String> sshKeyPairs, 
Map<Long, IpAddresses> requestedIps, IpAddresses defaultIps, Boolean displayVm, 
String keyboard,
         List<Long> affinityGroupIdList, Map<String, String> customParameters, 
String customId, Map<String, Map<Integer, String>> dhcpOptionMap,
         Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap,
         Map<String, String> userVmOVFProperties, boolean 
dynamicScalingEnabled, Long overrideDiskOfferingId) throws 
InsufficientCapacityException,
@@ -377,7 +377,7 @@ public interface UserVmService {
      */
     UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering 
serviceOffering, VirtualMachineTemplate template, List<Long> networkIdList, 
Account owner,
         String hostName, String displayName, Long diskOfferingId, Long 
diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, 
String userData,
-        String sshKeyPair, Map<Long, IpAddresses> requestedIps, IpAddresses 
defaultIps, Boolean displayVm, String keyboard, List<Long> affinityGroupIdList,
+        List<String> sshKeyPairs, Map<Long, IpAddresses> requestedIps, 
IpAddresses defaultIps, Boolean displayVm, String keyboard, List<Long> 
affinityGroupIdList,
         Map<String, String> customParameters, String customId, Map<String, 
Map<Integer, String>> dhcpOptionMap, Map<Long, DiskOffering> 
dataDiskTemplateToDiskOfferingMap,
         Map<String, String> 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 5fbbc87..45dc7a2 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 49444d5..f237983 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 90b999d..41411b0 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<String> 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 class DeployVMCmd extends 
BaseAsyncCreateCustomIdCmd implements SecurityG
         return name;
     }
 
-    public String getSSHKeyPairName() {
-        return sshKeyPairName;
+    public List<String> getSSHKeyPairNames() {
+        List<String> sshKeyPairs = new ArrayList<String>();
+        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 ce481d8..41509b0 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.user.Account;
 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<String> 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<String> getNames() {
+        List<String> keypairnames = new ArrayList<String>();
+        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 3483c17..835c30c 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 class UserVmResponse extends 
BaseResponseWithTagInformation implements Co
         return instanceName;
     }
 
-    public String getKeyPairName() {
-        return keyPairName;
+    public String getKeyPairNames() {
+        return keyPairNames;
     }
 
     public Set<AffinityGroupResponse> getAffinityGroupList() {
@@ -848,8 +848,8 @@ public class UserVmResponse extends 
BaseResponseWithTagInformation implements Co
         this.tags = tags;
     }
 
-    public void setKeyPairName(String keyPairName) {
-        this.keyPairName = keyPairName;
+    public void setKeyPairNames(String keyPairNames) {
+        this.keyPairNames = keyPairNames;
     }
 
     public void setAffinityGroupList(Set<AffinityGroupResponse> 
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 e035e96..b9941dc 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<SSHKeyPairVO, Long> {
 
     public SSHKeyPairVO findByPublicKey(long accountId, long domainId, String 
publicKey);
 
+    public List<SSHKeyPairVO> findByNames(long accountId, long domainId, 
List<String> 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 1a773ce..6ad7600 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 @@ package com.cloud.user.dao;
 import java.util.List;
 
 
+import com.cloud.utils.db.Filter;
 import org.springframework.stereotype.Component;
 
 import com.cloud.user.SSHKeyPairVO;
@@ -64,6 +65,16 @@ public class SSHKeyPairDaoImpl extends 
GenericDaoBase<SSHKeyPairVO, Long> implem
     }
 
     @Override
+    public List<SSHKeyPairVO> findByNames(long accountId, long domainId, 
List<String> names) {
+        SearchCriteria<SSHKeyPairVO> 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<SSHKeyPairVO> sc = createSearchCriteria();
         sc.addAnd("publicKey", SearchCriteria.Op.EQ, publicKey);
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 e0b37e4..421dcf4 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 class VMInstanceVO implements VirtualMachine, 
FiniteStateObject<State, Vi
 
     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<String, String> 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 1eeb208..fc53758 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 dd5adf6..1c147e2 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 @@ public class KubernetesClusterResourceModifierActionWorker 
extends KubernetesClu
             logAndThrow(Level.ERROR, "Failed to read Kubernetes node 
configuration file", e);
         }
         String base64UserData = 
Base64.encodeBase64String(k8sNodeConfig.getBytes(com.cloud.utils.StringUtils.getPreferredCharset()));
+        List<String> keypairs = new ArrayList<String>();
+        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 938afaf..57daa6d 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 @@ public class KubernetesClusterStartWorker extends 
KubernetesClusterResourceModif
             logAndThrow(Level.ERROR, "Failed to read Kubernetes control node 
configuration file", e);
         }
         String base64UserData = 
Base64.encodeBase64String(k8sControlNodeConfig.getBytes(com.cloud.utils.StringUtils.getPreferredCharset()));
+        List<String> keypairs = new ArrayList<String>();
+        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 @@ public class KubernetesClusterStartWorker extends 
KubernetesClusterResourceModif
             logAndThrow(Level.ERROR, "Failed to read Kubernetes control 
configuration file", e);
         }
         String base64UserData = 
Base64.encodeBase64String(k8sControlNodeConfig.getBytes(com.cloud.utils.StringUtils.getPreferredCharset()));
+        List<String> keypairs = new ArrayList<String>();
+        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 75ad4b0..9cae5cc 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 @@ public class QueryManagerImpl extends 
MutualExclusiveIdsManagerBase implements Q
         }
 
         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 ed52b03..9eee2d6 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 class UserVmJoinDaoImpl extends 
GenericDaoBaseWithTagInformation<UserVmJo
 
         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 5f28119..43d6b58 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 class UserVmJoinVO extends 
BaseViewWithTagInformationVO implements Contro
         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 8b266b9..e0c1c72 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.Account;
 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 class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         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 class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
             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<SSHKeyPairVO> 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 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         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 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         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 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         } 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 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
     @ActionEvent(eventType = EventTypes.EVENT_VM_CREATE, eventDescription = 
"deploying Vm", create = true)
     public UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, 
ServiceOffering serviceOffering, VirtualMachineTemplate template, List<Long> 
securityGroupIdList,
             Account owner, String hostName, String displayName, Long 
diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, 
HTTPMethod httpmethod,
-            String userData, String sshKeyPair, Map<Long, IpAddresses> 
requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, 
List<Long> affinityGroupIdList,
+            String userData, List<String> sshKeyPairs, Map<Long, IpAddresses> 
requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, 
List<Long> affinityGroupIdList,
             Map<String, String> customParametes, String customId, Map<String, 
Map<Integer, String>> dhcpOptionMap,
             Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap, 
Map<String, String> userVmOVFProperties, boolean dynamicScalingEnabled, Long 
overrideDiskOfferingId) throws InsufficientCapacityException, 
ConcurrentOperationException, ResourceUnavailableException,
     StorageUnavailableException, ResourceAllocationException {
@@ -3453,7 +3461,7 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         }
 
         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 class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
     @ActionEvent(eventType = EventTypes.EVENT_VM_CREATE, eventDescription = 
"deploying Vm", create = true)
     public UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, 
ServiceOffering serviceOffering, VirtualMachineTemplate template, List<Long> 
networkIdList,
             List<Long> securityGroupIdList, Account owner, String hostName, 
String displayName, Long diskOfferingId, Long diskSize, String group, 
HypervisorType hypervisor,
-            HTTPMethod httpmethod, String userData, String sshKeyPair, 
Map<Long, IpAddresses> requestedIps, IpAddresses defaultIps, Boolean displayVm, 
String keyboard,
+            HTTPMethod httpmethod, String userData, List<String> sshKeyPairs, 
Map<Long, IpAddresses> requestedIps, IpAddresses defaultIps, Boolean displayVm, 
String keyboard,
             List<Long> affinityGroupIdList, Map<String, String> 
customParameters, String customId, Map<String, Map<Integer, String>> 
dhcpOptionMap,
             Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap, 
Map<String, String> userVmOVFProperties, boolean dynamicScalingEnabled, Long 
overrideDiskOfferingId) throws InsufficientCapacityException, 
ConcurrentOperationException,
     ResourceUnavailableException, StorageUnavailableException, 
ResourceAllocationException {
@@ -3564,7 +3572,7 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         }
 
         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 class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
     @ActionEvent(eventType = EventTypes.EVENT_VM_CREATE, eventDescription = 
"deploying Vm", create = true)
     public UserVm createAdvancedVirtualMachine(DataCenter zone, 
ServiceOffering serviceOffering, VirtualMachineTemplate template, List<Long> 
networkIdList, Account owner,
             String hostName, String displayName, Long diskOfferingId, Long 
diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, 
String userData,
-            String sshKeyPair, Map<Long, IpAddresses> requestedIps, 
IpAddresses defaultIps, Boolean displayvm, String keyboard, List<Long> 
affinityGroupIdList,
+            List<String> sshKeyPairs, Map<Long, IpAddresses> requestedIps, 
IpAddresses defaultIps, Boolean displayvm, String keyboard, List<Long> 
affinityGroupIdList,
             Map<String, String> customParametrs, String customId, Map<String, 
Map<Integer, String>> dhcpOptionsMap, Map<Long, DiskOffering> 
dataDiskTemplateToDiskOfferingMap,
             Map<String, String> userVmOVFPropertiesMap, boolean 
dynamicScalingEnabled, String type, Long overrideDiskOfferingId) throws 
InsufficientCapacityException, ConcurrentOperationException, 
ResourceUnavailableException,
     StorageUnavailableException, ResourceAllocationException {
@@ -3623,9 +3631,8 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
             }
         }
         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 class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
     @DB
     private UserVm createVirtualMachine(DataCenter zone, ServiceOffering 
serviceOffering, VirtualMachineTemplate tmplt, String hostName, String 
displayName, Account owner,
                                         Long diskOfferingId, Long diskSize, 
List<NetworkVO> networkList, List<Long> securityGroupIdList, String group, 
HTTPMethod httpmethod, String userData,
-                                        String sshKeyPair, HypervisorType 
hypervisor, Account caller, Map<Long, IpAddresses> requestedIps, IpAddresses 
defaultIps, Boolean isDisplayVm, String keyboard,
+                                        List<String> sshKeyPairs, 
HypervisorType hypervisor, Account caller, Map<Long, IpAddresses> requestedIps, 
IpAddresses defaultIps, Boolean isDisplayVm, String keyboard,
                                         List<Long> affinityGroupIdList, 
Map<String, String> customParameters, String customId, Map<String, Map<Integer, 
String>> dhcpOptionMap,
                                         Map<Long, DiskOffering> 
datadiskTemplateToDiskOfferringMap,
                                         Map<String, String> 
userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String type, Long 
overrideDiskOfferingId) throws InsufficientCapacityException, 
ResourceUnavailableException,
@@ -3945,14 +3952,16 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
 
         // 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<SSHKeyPairVO> 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<String, List<NicProfile>> networkNicMap = new 
LinkedHashMap<>();
@@ -4014,7 +4023,7 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
                         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 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         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 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
 
     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<String, List<NicProfile>> networkNicMap,
+                                  final long accountId, final long userId, 
final ServiceOffering offering, final boolean isIso, final String 
sshPublicKeys, final LinkedHashMap<String, List<NicProfile>> networkNicMap,
                                   final long id, final String instanceName, 
final String uuidName, final HypervisorType hypervisorType, final Map<String, 
String> customParameters,
                                   final Map<String, Map<Integer, String>> 
extraDhcpOptionMap, final Map<Long, DiskOffering> 
dataDiskTemplateToDiskOfferingMap,
-                                  final Map<String, String> 
userVmOVFPropertiesMap, final VirtualMachine.PowerState powerState, final 
boolean dynamicScalingEnabled, String type, final Long rootDiskOfferingId) 
throws InsufficientCapacityException {
+                                  final Map<String, String> 
userVmOVFPropertiesMap, final VirtualMachine.PowerState powerState, final 
boolean dynamicScalingEnabled, String type, final Long rootDiskOfferingId, 
String sshkeypairs) throws InsufficientCapacityException {
         return Transaction.execute(new 
TransactionCallbackWithException<UserVmVO, InsufficientCapacityException>() {
             @Override
             public UserVmVO doInTransaction(TransactionStatus status) throws 
InsufficientCapacityException {
@@ -4265,8 +4274,12 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
                     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 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
 
     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<String, List<NicProfile>> networkNicMap,
+            final long accountId, final long userId, final ServiceOfferingVO 
offering, final boolean isIso, final String sshPublicKeys, final 
LinkedHashMap<String, List<NicProfile>> networkNicMap,
             final long id, final String instanceName, final String uuidName, 
final HypervisorType hypervisorType, final Map<String, String> 
customParameters, final Map<String,
             Map<Integer, String>> extraDhcpOptionMap, final Map<Long, 
DiskOffering> dataDiskTemplateToDiskOfferingMap,
-            Map<String, String> userVmOVFPropertiesMap, final boolean 
dynamicScalingEnabled, String type, final Long rootDiskOfferingId) throws 
InsufficientCapacityException {
+            Map<String, String> 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<String, 
String> customParameters) throws InvalidParameterValueException
@@ -5689,7 +5702,7 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         Long size = cmd.getSize();
         String group = cmd.getGroup();
         String userData = cmd.getUserData();
-        String sshKeyPairName = cmd.getSSHKeyPairName();
+        List<String> sshKeyPairNames = cmd.getSSHKeyPairNames();
         Boolean displayVm = cmd.isDisplayVm();
         String keyboard = cmd.getKeyboard();
         Map<Long, DiskOffering> dataDiskTemplateToDiskOfferingMap = 
cmd.getDataDiskTemplateToDiskOfferingMap();
@@ -5699,14 +5712,14 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
                 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 class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
                     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<VMTemplateVO> child_templates = 
_templateDao.listByParentTemplatetId(templateId);
         for (VMTemplateVO tmpl: child_templates){
@@ -5750,7 +5764,6 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
                 }
             }
         }
-
         return vm;
     }
 
@@ -7694,13 +7707,13 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
     }
 
     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 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
     @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<String, String> 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 class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         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 02b0648..cda1382 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: <br> <code><b> ssh -i [ssh_key] -p [port_number] 
cloud@[public_ip_address] </b></code> <br><br> where, <br> 
<code><b>ssh_key:</b></code> 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. <br> <code><b>port_number:</b></cod [...]
diff --git a/ui/src/components/view/InfoCard.vue 
b/ui/src/components/view/InfoCard.vue
index 6b61dad..fc36154 100644
--- a/ui/src/components/view/InfoCard.vue
+++ b/ui/src/components/view/InfoCard.vue
@@ -361,11 +361,13 @@
             <router-link :to="{ path: '/vmgroup/' + resource.groupid }">{{ 
resource.group || resource.groupid }}</router-link>
           </div>
         </div>
-        <div class="resource-detail-item" v-if="resource.keypair">
-          <div class="resource-detail-item__label">{{ $t('label.keypair') 
}}</div>
+        <div class="resource-detail-item" v-if="resource.keypairs">
+          <div class="resource-detail-item__label">{{ $t('label.keypairs') 
}}</div>
           <div class="resource-detail-item__details">
             <a-icon type="key" />
-            <router-link :to="{ path: '/ssh/' + resource.keypair }">{{ 
resource.keypair }}</router-link>
+            <li v-for="keypair in keypairs" :key="keypair">
+              <router-link :to="{ path: '/ssh/' + keypair }" 
style="margin-right: 5px">{{ keypair }}</router-link>
+            </li>
           </div>
         </div>
         <div class="resource-detail-item" v-if="resource.virtualmachineid">
@@ -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 f1f2340..178ac9c 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 211710b..964364c 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 0000000..e09745a
--- /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.
+
+<template>
+  <a-form class="form" v-ctrl-enter="handleSubmit">
+    <p v-html="$t('message.desc.reset.ssh.key.pair')" />
+    <a-spin :spinning="loading">
+
+      <div class="form__item">
+        <a-input-search
+          style="margin-bottom: 10px;"
+          :placeholder="$t('label.search')"
+          v-model="filter"
+          @search="handleSearch"
+          autoFocus />
+      </div>
+
+      <div class="form__item">
+        <a-table
+          size="small"
+          :loading="loading"
+          :columns="columns"
+          :dataSource="items"
+          :rowKey="record => record.name"
+          :pagination="{showSizeChanger: true, total: total}"
+          :rowSelection="{selectedRowKeys: selectedRowKeys, onChange: 
onSelectChange}"
+          @change="handleTableChange"
+          @handle-search-filter="handleTableChange"
+          style="overflow-y: auto" >
+
+          <template v-slot:account><a-icon type="user" /> {{ 
$t('label.account') }}</template>
+          <template v-slot:domain><a-icon type="block" /> {{ 
$t('label.domain') }}</template>
+
+        </a-table>
+      </div>
+
+      <div :span="24" class="action-button">
+        <a-button @click="closeAction">{{ this.$t('label.cancel') }}</a-button>
+        <a-button :loading="loading" ref="submit" type="primary" 
@click="handleSubmit">{{ this.$t('label.ok') }}</a-button>
+      </div>
+
+    </a-spin>
+  </a-form>
+</template>
+
+<script>
+import { api } from '@/api'
+import { genericCompare } from '@/utils/sort.js'
+
+export default {
+  name: 'ResetSshKeyPair',
+  props: {
+    resource: {
+      type: Object,
+      required: true
+    }
+  },
+  data () {
+    return {
+      items: [],
+      total: 0,
+      columns: [
+        {
+          dataIndex: 'name',
+          title: this.$t('label.name'),
+          sorter: function (a, b) { return genericCompare(a[this.dataIndex] || 
'', b[this.dataIndex] || '') },
+          width: '40%'
+        },
+        {
+          dataIndex: 'account',
+          slots: { title: 'account' },
+          width: '30%'
+        },
+        {
+          dataIndex: 'domain',
+          slots: { title: 'domain' },
+          width: '30%'
+        }
+      ],
+      selectedRowKeys: [],
+      options: {
+        page: 1,
+        pageSize: 10,
+        keyword: '',
+        response: 'json'
+      },
+      filter: '',
+      loading: false
+    }
+  },
+  created () {
+    this.fetchData()
+    if (this.resource.keypairs) {
+      this.selectedRowKeys = this.resource.keypairs.split(',')
+    }
+  },
+  methods: {
+    fetchData () {
+      this.loading = true
+      this.items = []
+      this.total = 0
+      api('listSSHKeyPairs', this.options).then(response => {
+        this.total = response.listsshkeypairsresponse.count
+        if (this.total !== 0) {
+          this.items = response.listsshkeypairsresponse.sshkeypair
+        }
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    onSelectChange (selectedRowKeys) {
+      this.selectedRowKeys = selectedRowKeys
+    },
+    handleSearch (keyword) {
+      this.filter = keyword
+      this.options.keyword = keyword
+      this.fetchData()
+    },
+    handleTableChange (pagination) {
+      this.options.page = pagination.current
+      this.options.pageSize = pagination.pageSize
+      this.fetchData()
+    },
+    handleSubmit () {
+      if (this.loading) return
+      this.loading = true
+      api('resetSSHKeyForVirtualMachine', {
+        id: this.resource.id,
+        keypairs: this.selectedRowKeys.join(',')
+      }).then(response => {
+        const jobId = response.resetSSHKeyforvirtualmachineresponse.jobid
+        const title = `${this.$t('label.reset.ssh.key.pair')}`
+        if (jobId) {
+          this.$pollJob({
+            jobId,
+            title,
+            description: this.resource.name,
+            successMessage: `${title} ${this.$t('label.success')}`,
+            loadingMessage: `${title} ${this.$t('label.in.progress')}`,
+            catchMessage: this.$t('error.fetching.async.job.result')
+          })
+        }
+        this.closeAction()
+      }).catch(error => {
+        this.$notifyError(error)
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    closeAction () {
+      this.$emit('close-action')
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.form {
+  width: 90vw;
+  @media (min-width: 800px) {
+    width: 45vw;
+  }
+}
+</style>
diff --git a/ui/src/views/compute/wizard/SshKeyPairSelection.vue 
b/ui/src/views/compute/wizard/SshKeyPairSelection.vue
index 222868e..79b0d1c 100644
--- a/ui/src/views/compute/wizard/SshKeyPairSelection.vue
+++ b/ui/src/views/compute/wizard/SshKeyPairSelection.vue
@@ -25,15 +25,16 @@
     <a-table
       :loading="loading"
       :columns="columns"
-      :dataSource="tableSource"
+      :dataSource="items"
       :rowSelection="rowSelection"
-      :customRow="onClickRow"
+      :rowKey="item => item.name"
       :pagination="false"
       size="middle"
-      :scroll="{ y: 225 }"
-    >
+      :scroll="{ y: 225 }" >
+
       <template v-slot:account><a-icon type="user" /> {{ $t('label.account') 
}}</template>
       <template v-slot:domain><a-icon type="block" /> {{ $t('label.domain') 
}}</template>
+
     </a-table>
     <div style="display: block; text-align: right;">
       <a-pagination
@@ -67,8 +68,8 @@ export default {
       default: () => 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)
-          }
-        }
-      }
     }
   }
 }

Reply via email to