diff --git a/agent/src/com/cloud/agent/resource/computing/LibvirtComputingResource.java b/agent/src/com/cloud/agent/resource/computing/LibvirtComputingResource.java index 46f381ff6ed..e537fbbf2d2 100644 --- a/agent/src/com/cloud/agent/resource/computing/LibvirtComputingResource.java +++ b/agent/src/com/cloud/agent/resource/computing/LibvirtComputingResource.java @@ -1279,6 +1279,13 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv DomainSnapshot snap = vm.snapshotLookupByName(snapshotName); snap.delete(0); } + + /*libvirt on RHEL6 doesn't handle resume event emitted from qemu*/ + vm = getDomain(cmd.getVmName()); + state = vm.getInfo().state; + if (state == DomainInfo.DomainState.VIR_DOMAIN_PAUSED) { + vm.resume(); + } } else { /*VM is not running, create a snapshot by ourself*/ final Script command = new Script(_manageSnapshotPath, _timeout, s_logger); @@ -1346,6 +1353,13 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv s_logger.debug(snapshot); DomainSnapshot snap = vm.snapshotLookupByName(snapshotName); snap.delete(0); + + /*libvirt on RHEL6 doesn't handle resume event emitted from qemu*/ + vm = getDomain(cmd.getVmName()); + state = vm.getInfo().state; + if (state == DomainInfo.DomainState.VIR_DOMAIN_PAUSED) { + vm.resume(); + } } else { command = new Script(_manageSnapshotPath, _timeout, s_logger); command.add("-d", snapshotPath); @@ -3868,12 +3882,18 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv File f =new File("/usr/bin/cloud-qemu-system-x86_64"); if (f.exists()) { return true; - } else { - f = new File("/usr/libexec/cloud-qemu-kvm"); - if (f.exists()) { - return true; - } + } + + f = new File("/usr/libexec/cloud-qemu-kvm"); + if (f.exists()) { + return true; } + + f = new File("/usr/bin/cloud-qemu-img"); + if (f.exists()) { + return true; + } + return false; } diff --git a/api/src/com/cloud/api/commands/CreateSSHKeyPairCmd.java b/api/src/com/cloud/api/commands/CreateSSHKeyPairCmd.java new file mode 100644 index 00000000000..dc5dfb1b298 --- /dev/null +++ b/api/src/com/cloud/api/commands/CreateSSHKeyPairCmd.java @@ -0,0 +1,52 @@ +package com.cloud.api.commands; + +import org.apache.log4j.Logger; + +import com.cloud.api.ApiConstants; +import com.cloud.api.BaseCmd; +import com.cloud.api.Implementation; +import com.cloud.api.Parameter; +import com.cloud.api.response.SSHKeyPairResponse; +import com.cloud.user.SSHKeyPair; + +@Implementation(description="Create a new keypair and returns the private key", responseObject=SSHKeyPairResponse.class) +public class CreateSSHKeyPairCmd extends BaseCmd { + public static final Logger s_logger = Logger.getLogger(CreateSSHKeyPairCmd.class.getName()); + private static final String s_name = "createkeypairresponse"; + + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name=ApiConstants.NAME, type=CommandType.STRING, required=true, description="Name of the keypair") + private String name; + + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getName() { + return name; + } + + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + SSHKeyPair r = _mgr.createSSHKeyPair(this); + SSHKeyPairResponse response = new SSHKeyPairResponse(r.getName(), r.getFingerprint(), r.getPrivateKey()); + response.setResponseName(getCommandName()); + response.setObjectName("keypair"); + this.setResponseObject(response); + } + + @Override + public String getCommandName() { + return s_name; + } +} diff --git a/api/src/com/cloud/api/commands/DeleteSSHKeyPairCmd.java b/api/src/com/cloud/api/commands/DeleteSSHKeyPairCmd.java new file mode 100644 index 00000000000..47a0e24358e --- /dev/null +++ b/api/src/com/cloud/api/commands/DeleteSSHKeyPairCmd.java @@ -0,0 +1,50 @@ +package com.cloud.api.commands; + +import org.apache.log4j.Logger; + +import com.cloud.api.ApiConstants; +import com.cloud.api.BaseCmd; +import com.cloud.api.Implementation; +import com.cloud.api.Parameter; +import com.cloud.api.response.SuccessResponse; + +@Implementation(description="Deletes a keypair by name", responseObject=SuccessResponse.class) +public class DeleteSSHKeyPairCmd extends BaseCmd { + public static final Logger s_logger = Logger.getLogger(CreateSSHKeyPairCmd.class.getName()); + private static final String s_name = "deletekeypairresponse"; + + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name=ApiConstants.NAME, type=CommandType.STRING, required=true, description="Name of the keypair") + private String name; + + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getName() { + return name; + } + + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + boolean result = _mgr.deleteSSHKeyPair(this); + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setSuccess(result); + this.setResponseObject(response); + } + + @Override + public String getCommandName() { + return s_name; + } +} diff --git a/api/src/com/cloud/api/commands/DeployVMCmd.java b/api/src/com/cloud/api/commands/DeployVMCmd.java index c3848f72cfd..8e0bfab0088 100644 --- a/api/src/com/cloud/api/commands/DeployVMCmd.java +++ b/api/src/com/cloud/api/commands/DeployVMCmd.java @@ -91,6 +91,9 @@ public class DeployVMCmd extends BaseAsyncCreateCmd { @Parameter(name=ApiConstants.NETWORK_IDS, type=CommandType.LIST, collectionType=CommandType.LONG, description="list of network ids used by virtual machine") private List networkIds; + @Parameter(name="keypair", type=CommandType.STRING, description="name of the ssh key pair used to login to the virtual machine") + private String sshKeyPairName; + // unexposed parameter needed for serializing/deserializing the command @Parameter(name=ApiConstants.PASSWORD, type=CommandType.STRING, expose=false) private String password; @@ -170,6 +173,10 @@ public class DeployVMCmd extends BaseAsyncCreateCmd { return name; } + public String getSSHKeyPairName() { + return sshKeyPairName; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// diff --git a/api/src/com/cloud/api/commands/GetVMPasswordCmd.java b/api/src/com/cloud/api/commands/GetVMPasswordCmd.java new file mode 100644 index 00000000000..45ecafcba7a --- /dev/null +++ b/api/src/com/cloud/api/commands/GetVMPasswordCmd.java @@ -0,0 +1,54 @@ +package com.cloud.api.commands; + +import java.security.InvalidParameterException; + +import org.apache.log4j.Logger; + +import com.cloud.api.ApiConstants; +import com.cloud.api.BaseCmd; +import com.cloud.api.Implementation; +import com.cloud.api.Parameter; +import com.cloud.api.response.GetVMPasswordResponse; + +@Implementation(responseObject=GetVMPasswordResponse.class, description="Returns an encrypted password for the VM") +public class GetVMPasswordCmd extends BaseCmd { + public static final Logger s_logger = Logger.getLogger(GetVMPasswordCmd.class.getName()); + private static final String s_name = "getvmpasswordresponse"; + + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name=ApiConstants.ID, type=CommandType.LONG, required=true, description="The ID of the virtual machine") + private Long id; + + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + String passwd = _mgr.getVMPassword(this); + if (passwd == null || passwd.equals("")) + throw new InvalidParameterException("No password for VM with id '" + getId() + "' found."); + + this.setResponseObject(new GetVMPasswordResponse(getCommandName(), passwd)); + } + + @Override + public String getCommandName() { + return s_name; + } + +} diff --git a/api/src/com/cloud/api/commands/ListSSHKeyPairsCmd.java b/api/src/com/cloud/api/commands/ListSSHKeyPairsCmd.java new file mode 100644 index 00000000000..4db3355dabe --- /dev/null +++ b/api/src/com/cloud/api/commands/ListSSHKeyPairsCmd.java @@ -0,0 +1,71 @@ +package com.cloud.api.commands; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Logger; + +import com.cloud.api.ApiConstants; +import com.cloud.api.BaseListCmd; +import com.cloud.api.Implementation; +import com.cloud.api.Parameter; +import com.cloud.api.response.ListResponse; +import com.cloud.api.response.SSHKeyPairResponse; +import com.cloud.user.SSHKeyPair; + +@Implementation(description="List registered keypairs", responseObject=SSHKeyPairResponse.class) +public class ListSSHKeyPairsCmd extends BaseListCmd { + public static final Logger s_logger = Logger.getLogger(ListSSHKeyPairsCmd.class.getName()); + private static final String s_name = "listsshkeypairsresponse"; + + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name=ApiConstants.NAME, type=CommandType.STRING, required=false, description="A key pair name to look for") + private String name; + + @Parameter(name="fingerprint", type=CommandType.STRING, required=false, description="A public key fingerprint to look for") + private String fingerprint; + + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getName() { + return name; + } + + public String getFingerprint() { + return fingerprint; + } + + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + List resultList = _mgr.listSSHKeyPairs(this); + List responses = new ArrayList(); + for (SSHKeyPair result : resultList) { + SSHKeyPairResponse r = new SSHKeyPairResponse(result.getName(), result.getFingerprint()); + r.setObjectName("keypair"); + responses.add(r); + } + + ListResponse response = new ListResponse(); + response.setResponses(responses); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } + + @Override + public String getCommandName() { + return s_name; + } + +} diff --git a/api/src/com/cloud/api/commands/RegisterSSHKeyPairCmd.java b/api/src/com/cloud/api/commands/RegisterSSHKeyPairCmd.java new file mode 100644 index 00000000000..6ef442f3e58 --- /dev/null +++ b/api/src/com/cloud/api/commands/RegisterSSHKeyPairCmd.java @@ -0,0 +1,60 @@ +package com.cloud.api.commands; + +import org.apache.log4j.Logger; + +import com.cloud.api.ApiConstants; +import com.cloud.api.BaseCmd; +import com.cloud.api.Implementation; +import com.cloud.api.Parameter; +import com.cloud.api.response.SSHKeyPairResponse; +import com.cloud.user.SSHKeyPair; + +@Implementation(description="Register a public key in a keypair under a certain name", responseObject=SSHKeyPairResponse.class) +public class RegisterSSHKeyPairCmd extends BaseCmd { + public static final Logger s_logger = Logger.getLogger(RegisterSSHKeyPairCmd.class.getName()); + private static final String s_name = "registerkeypairresponse"; + + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name=ApiConstants.NAME, type=CommandType.STRING, required=true, description="Name of the keypair") + private String name; + + @Parameter(name="publickey", type=CommandType.STRING, required=true, description="Public key material of the keypair") + private String publicKey; + + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getName() { + return name; + } + + public String getPublicKey() { + return publicKey; + } + + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + SSHKeyPair result = _mgr.registerSSHKeyPair(this); + SSHKeyPairResponse response = new SSHKeyPairResponse(result.getName(), result.getFingerprint()); + response.setResponseName(getCommandName()); + response.setObjectName("keypair"); + this.setResponseObject(response); + } + + @Override + public String getCommandName() { + return s_name; + } + +} diff --git a/api/src/com/cloud/api/response/GetVMPasswordResponse.java b/api/src/com/cloud/api/response/GetVMPasswordResponse.java new file mode 100644 index 00000000000..1c826649fd2 --- /dev/null +++ b/api/src/com/cloud/api/response/GetVMPasswordResponse.java @@ -0,0 +1,27 @@ +package com.cloud.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class GetVMPasswordResponse extends BaseResponse { + + @SerializedName("encryptedpassword") @Param(description="The encrypted password of the VM") + private String encryptedPassword; + + public GetVMPasswordResponse() {} + + public GetVMPasswordResponse(String responseName, String encryptedPassword) { + setResponseName(responseName); + setObjectName("password"); + setEncryptedPassword(encryptedPassword); + } + + public String getEncryptedPassword() { + return encryptedPassword; + } + + public void setEncryptedPassword(String encryptedPassword) { + this.encryptedPassword = encryptedPassword; + } + +} diff --git a/api/src/com/cloud/api/response/SSHKeyPairResponse.java b/api/src/com/cloud/api/response/SSHKeyPairResponse.java new file mode 100644 index 00000000000..de103eda715 --- /dev/null +++ b/api/src/com/cloud/api/response/SSHKeyPairResponse.java @@ -0,0 +1,54 @@ +package com.cloud.api.response; + +import com.cloud.api.ApiConstants; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class SSHKeyPairResponse extends BaseResponse { + + @SerializedName(ApiConstants.NAME) @Param(description="Name of the keypair") + private String name; + + @SerializedName("fingerprint") @Param(description="Fingerprint of the public key") + private String fingerprint; + + @SerializedName("privatekey") @Param(description="Private key") + private String privateKey; + + public SSHKeyPairResponse() {} + + public SSHKeyPairResponse(String name, String fingerprint) { + this(name, fingerprint, null); + } + + public SSHKeyPairResponse(String name, String fingerprint, String privateKey) { + this.name = name; + this.fingerprint = fingerprint; + this.privateKey = privateKey; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getFingerprint() { + return fingerprint; + } + + public void setFingerprint(String fingerprint) { + this.fingerprint = fingerprint; + } + + public String getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } + +} diff --git a/api/src/com/cloud/server/ManagementService.java b/api/src/com/cloud/server/ManagementService.java index 8ca4895b8a9..3b5f929e48e 100644 --- a/api/src/com/cloud/server/ManagementService.java +++ b/api/src/com/cloud/server/ManagementService.java @@ -26,10 +26,13 @@ import java.util.Set; import com.cloud.alert.Alert; import com.cloud.api.ServerApiException; import com.cloud.api.commands.CreateDomainCmd; +import com.cloud.api.commands.CreateSSHKeyPairCmd; import com.cloud.api.commands.DeleteDomainCmd; import com.cloud.api.commands.DeletePreallocatedLunCmd; +import com.cloud.api.commands.DeleteSSHKeyPairCmd; import com.cloud.api.commands.ExtractVolumeCmd; import com.cloud.api.commands.GetCloudIdentifierCmd; +import com.cloud.api.commands.GetVMPasswordCmd; import com.cloud.api.commands.ListAccountsCmd; import com.cloud.api.commands.ListAlertsCmd; import com.cloud.api.commands.ListAsyncJobsCmd; @@ -50,6 +53,7 @@ import com.cloud.api.commands.ListPodsByCmd; import com.cloud.api.commands.ListPreallocatedLunsCmd; import com.cloud.api.commands.ListPublicIpAddressesCmd; import com.cloud.api.commands.ListRoutersCmd; +import com.cloud.api.commands.ListSSHKeyPairsCmd; import com.cloud.api.commands.ListServiceOfferingsCmd; import com.cloud.api.commands.ListStoragePoolsCmd; import com.cloud.api.commands.ListSystemVMsCmd; @@ -64,6 +68,7 @@ import com.cloud.api.commands.ListZonesByCmd; import com.cloud.api.commands.RebootSystemVmCmd; import com.cloud.api.commands.RegisterCmd; import com.cloud.api.commands.RegisterPreallocatedLunCmd; +import com.cloud.api.commands.RegisterSSHKeyPairCmd; import com.cloud.api.commands.StartSystemVMCmd; import com.cloud.api.commands.StopSystemVmCmd; import com.cloud.api.commands.UpdateDomainCmd; @@ -96,6 +101,7 @@ import com.cloud.storage.StoragePool; import com.cloud.storage.Volume; import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; +import com.cloud.user.SSHKeyPair; import com.cloud.user.UserAccount; import com.cloud.uservm.UserVm; import com.cloud.utils.Pair; @@ -406,4 +412,40 @@ public interface ManagementService { public Long saveCompletedEvent(Long userId, Long accountId, String level, String type, String description, long startEventId); + /** + * Search registered key pairs for the logged in user. + * @param cmd The api command class. + * @return The list of key pairs found. + */ + List listSSHKeyPairs(ListSSHKeyPairsCmd cmd); + + /** + * Registers a key pair for a given public key. + * @param cmd The api command class. + * @return A VO with the key pair name and a finger print for the public key. + */ + SSHKeyPair registerSSHKeyPair(RegisterSSHKeyPairCmd cmd); + + /** + * Creates a new + * @param cmd The api command class. + * @return A VO containing the key pair name, finger print for the public key + * and the private key material of the key pair. + */ + SSHKeyPair createSSHKeyPair(CreateSSHKeyPairCmd cmd); + + /** + * Deletes a key pair by name. + * @param cmd The api command class. + * @return True on success. False otherwise. + */ + boolean deleteSSHKeyPair(DeleteSSHKeyPairCmd cmd); + + /** + * Finds and returns an encrypted password for a VM. + * @param cmd The api command class. + * @return The encrypted password. + */ + String getVMPassword(GetVMPasswordCmd cmd); + } diff --git a/api/src/com/cloud/user/AccountService.java b/api/src/com/cloud/user/AccountService.java index 4858a285797..49fa836369c 100644 --- a/api/src/com/cloud/user/AccountService.java +++ b/api/src/com/cloud/user/AccountService.java @@ -145,7 +145,8 @@ public interface AccountService { Account getActiveAccount(String accountName, Long domainId); - Account getAccount(Long accountId); + Account getActiveAccount(Long accountId); + Account getAccount(Long accountId); } diff --git a/api/src/com/cloud/user/SSHKeyPair.java b/api/src/com/cloud/user/SSHKeyPair.java new file mode 100644 index 00000000000..205b3cc2ec5 --- /dev/null +++ b/api/src/com/cloud/user/SSHKeyPair.java @@ -0,0 +1,32 @@ +package com.cloud.user; + +import com.cloud.acl.ControlledEntity; + +public interface SSHKeyPair extends ControlledEntity { + + /** + * @return The id of the key pair. + */ + public long getId(); + + /** + * @return The given name of the key pair. + */ + public String getName(); + + /** + * @return The finger print of the public key. + */ + public String getFingerprint(); + + /** + * @return The public key of the key pair. + */ + public String getPublicKey(); + + /** + * @return The private key of the key pair. + */ + public String getPrivateKey(); + +} diff --git a/api/src/com/cloud/uservm/UserVm.java b/api/src/com/cloud/uservm/UserVm.java index b9589752d7f..4b0c90abe73 100755 --- a/api/src/com/cloud/uservm/UserVm.java +++ b/api/src/com/cloud/uservm/UserVm.java @@ -68,4 +68,7 @@ public interface UserVm extends VirtualMachine, ControlledEntity { Long getDomainRouterId(); void setUserData(String userData); + + String getDetail(String name); + } diff --git a/client/tomcatconf/commands.properties.in b/client/tomcatconf/commands.properties.in index 5a294d8730a..f0c9608dd6e 100755 --- a/client/tomcatconf/commands.properties.in +++ b/client/tomcatconf/commands.properties.in @@ -45,6 +45,7 @@ changeServiceForVirtualMachine=com.cloud.api.commands.UpgradeVMCmd;15 updateVirtualMachine=com.cloud.api.commands.UpdateVMCmd;15 recoverVirtualMachine=com.cloud.api.commands.RecoverVMCmd;3 listVirtualMachines=com.cloud.api.commands.ListVMsCmd;15 +getVMPassword=com.cloud.api.commands.GetVMPasswordCmd;15 #### snapshot commands createSnapshot=com.cloud.api.commands.CreateSnapshotCmd;15 @@ -246,3 +247,9 @@ createNetwork=com.cloud.api.commands.CreateNetworkCmd;15 deleteNetwork=com.cloud.api.commands.DeleteNetworkCmd;15 listNetworks=com.cloud.api.commands.ListNetworksCmd;15 restartNetwork=com.cloud.api.commands.RestartNetworkCmd;15 + +#### SSH key pair commands +registerSSHKeyPair=com.cloud.api.commands.RegisterSSHKeyPairCmd;15 +createSSHKeyPair=com.cloud.api.commands.CreateSSHKeyPairCmd;15 +deleteSSHKeyPair=com.cloud.api.commands.DeleteSSHKeyPairCmd;15 +listSSHKeyPairs=com.cloud.api.commands.ListSSHKeyPairsCmd;15 diff --git a/cloud.spec b/cloud.spec index bd2ed0e72e0..03e6228744d 100644 --- a/cloud.spec +++ b/cloud.spec @@ -218,14 +218,19 @@ Requires: jpackage-utils Requires: %{name}-daemonize Requires: /sbin/service Requires: /sbin/chkconfig + +%if 0%{?rhel} >= 6 +Requires: cloud-kvm +%else Requires: kvm +%endif + %if 0%{?fedora} >= 12 Requires: qemu-cloud-system-x86 Requires: qemu-cloud-img %endif %if 0%{?rhel} >= 6 -Requires: cloud-qemu-kvm Requires: cloud-qemu-img %endif @@ -492,6 +497,9 @@ fi %{_javadir}/%{name}-commons-discovery.jar %{_javadir}/%{name}-iControl.jar %{_javadir}/%{name}-wsdl4j.jar +%{_javadir}/%{name}-bcprov-jdk16-1.45.jar +%{_javadir}/%{name}-jsch-0.1.42.jar + %files core %defattr(0644,root,root,0755) diff --git a/core/src/com/cloud/user/SSHKeyPairVO.java b/core/src/com/cloud/user/SSHKeyPairVO.java new file mode 100644 index 00000000000..bfec3bc3830 --- /dev/null +++ b/core/src/com/cloud/user/SSHKeyPairVO.java @@ -0,0 +1,101 @@ +package com.cloud.user; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Transient; + +@Entity +@Table(name="ssh_keypairs") +public class SSHKeyPairVO implements SSHKeyPair { + + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Column(name="id") + private Long id = null; + + @Column(name="account_id") + private long accountId; + + @Column(name="domain_id") + private long domainId; + + @Column(name="keypair_name") + private String name; + + @Column(name="fingerprint") + private String fingerprint; + + @Column(name="public_key") + private String publicKey; + + @Transient + private String privateKey; + + @Override + public long getId() { + return id; + } + + @Override + public long getAccountId() { + return accountId; + } + + @Override + public long getDomainId() { + return domainId; + } + + @Override + public String getFingerprint() { + return fingerprint; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getPublicKey() { + return publicKey; + } + + @Override + public String getPrivateKey() { + return privateKey; + } + + public void setId(Long id) { + this.id = id; + } + + public void setAccountId(long accountId) { + this.accountId = accountId; + } + + public void setDomainId(long domainId) { + this.domainId = domainId; + } + + public void setName(String name) { + this.name = name; + } + + public void setFingerprint(String fingerprint) { + this.fingerprint = fingerprint; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } + +} diff --git a/core/src/com/cloud/vm/UserVmDetailVO.java b/core/src/com/cloud/vm/UserVmDetailVO.java new file mode 100644 index 00000000000..cb574c57337 --- /dev/null +++ b/core/src/com/cloud/vm/UserVmDetailVO.java @@ -0,0 +1,67 @@ +package com.cloud.vm; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name="user_vm_details") +public class UserVmDetailVO { + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Column(name="id") + private long id; + + @Column(name="vm_id") + private long vmId; + + @Column(name="name") + private String name; + + @Column(name="value") + private String value; + + public UserVmDetailVO() {} + + public UserVmDetailVO(long vmId, String name, String value) { + this.vmId = vmId; + this.name = name; + this.value = value; + } + + public long getId() { + return id; + } + + public long getVmId() { + return vmId; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + public void setId(long id) { + this.id = id; + } + + public void setVmId(long vmId) { + this.vmId = vmId; + } + + public void setName(String name) { + this.name = name; + } + + public void setValue(String value) { + this.value = value; + } + +} diff --git a/core/src/com/cloud/vm/UserVmVO.java b/core/src/com/cloud/vm/UserVmVO.java index 89ba6f70e5d..eff39465915 100755 --- a/core/src/com/cloud/vm/UserVmVO.java +++ b/core/src/com/cloud/vm/UserVmVO.java @@ -17,11 +17,15 @@ */ package com.cloud.vm; +import java.util.HashMap; +import java.util.Map; + import javax.persistence.Column; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.Table; +import javax.persistence.Transient; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.uservm.UserVm; @@ -67,6 +71,9 @@ public class UserVmVO extends VMInstanceVO implements UserVm { transient String password; + @Transient + Map details; + @Override public String getPassword() { return password; @@ -75,8 +82,8 @@ public class UserVmVO extends VMInstanceVO implements UserVm { public void setPassword(String password) { this.password = password; } - - @Override + + @Override public String getGuestIpAddress() { return guestIpAddress; } @@ -148,10 +155,12 @@ public class UserVmVO extends VMInstanceVO implements UserVm { long domainId, long accountId, long serviceOfferingId, - String userData, String name) { + String userData, + String name) { super(id, serviceOfferingId, name, instanceName, Type.User, templateId, hypervisorType, guestOsId, domainId, accountId, haEnabled); this.userData = userData; this.displayName = displayName != null ? displayName : null; + this.details = new HashMap(); } protected UserVmVO() { @@ -204,4 +213,26 @@ public class UserVmVO extends VMInstanceVO implements UserVm { public void setDisplayName(String displayName) { this.displayName = displayName; } + + public Map getDetails() { + return details; + } + + @Override + public String getDetail(String name) { + assert (details != null) : "Did you forget to load the details?"; + + return details != null ? details.get(name) : null; + } + + public void setDetail(String name, String value) { + assert (details != null) : "Did you forget to load the details?"; + + details.put(name, value); + } + + public void setDetails(Map details) { + this.details = details; + } + } diff --git a/deps/.project b/deps/.project new file mode 100644 index 00000000000..2d10b0c4b06 --- /dev/null +++ b/deps/.project @@ -0,0 +1,11 @@ + + + deps + + + + + + + + diff --git a/deps/cloud-bcprov-jdk16-1.45.jar b/deps/cloud-bcprov-jdk16-1.45.jar new file mode 100644 index 00000000000..38685d51051 Binary files /dev/null and b/deps/cloud-bcprov-jdk16-1.45.jar differ diff --git a/deps/cloud-jsch-0.1.42.jar b/deps/cloud-jsch-0.1.42.jar new file mode 100644 index 00000000000..c65eff0954f Binary files /dev/null and b/deps/cloud-jsch-0.1.42.jar differ diff --git a/patches/systemvm/debian/config/etc/logrotate.conf b/patches/systemvm/debian/config/etc/logrotate.conf new file mode 100644 index 00000000000..f5a660964d3 --- /dev/null +++ b/patches/systemvm/debian/config/etc/logrotate.conf @@ -0,0 +1,37 @@ +# see "man logrotate" for details +# rotate log files daily +daily + +# keep 5 days worth +rotate 5 + +# create new (empty) log files after rotating old ones +create + +# use date as a suffix of the rotated file +dateext + +# uncomment this if you want your log files compressed +#compress + +# max size 50M +size 50M + +# RPM packages drop log rotation information into this directory +include /etc/logrotate.d + +# no packages own wtmp and btmp -- we'll rotate them here +/var/log/wtmp { + monthly + create 0664 root utmp + rotate 1 +} + +/var/log/btmp { + missingok + monthly + create 0600 root utmp + rotate 1 +} + +# system-specific logs may be also be configured here. diff --git a/patches/systemvm/debian/config/etc/logrotate.d/apache2 b/patches/systemvm/debian/config/etc/logrotate.d/apache2 new file mode 100644 index 00000000000..f2dd950bf26 --- /dev/null +++ b/patches/systemvm/debian/config/etc/logrotate.d/apache2 @@ -0,0 +1,13 @@ +/var/log/apache2/*.log { + weekly + missingok + rotate 52 + compress + delaycompress + notifempty + create 640 root adm + sharedscripts + postrotate + /etc/init.d/apache2 reload > /dev/null + endscript +} diff --git a/patches/systemvm/debian/config/etc/logrotate.d/dnsmasq b/patches/systemvm/debian/config/etc/logrotate.d/dnsmasq new file mode 100644 index 00000000000..f448420e176 --- /dev/null +++ b/patches/systemvm/debian/config/etc/logrotate.d/dnsmasq @@ -0,0 +1,13 @@ +/var/log/dnsmasq.log { + daily + missingok + rotate 5 + notifempty + delaycompress + sharedscripts + postrotate + [ ! -f /var/run/dnsmasq.pid ] || kill -USR2 `cat /var/run/dnsmasq.pid` + endscript + create 0640 nobody root +} + diff --git a/patches/systemvm/debian/config/etc/logrotate.d/haproxy b/patches/systemvm/debian/config/etc/logrotate.d/haproxy new file mode 100644 index 00000000000..858fe2a1c3a --- /dev/null +++ b/patches/systemvm/debian/config/etc/logrotate.d/haproxy @@ -0,0 +1,10 @@ +/var/log/haproxy.log { + daily + rotate 5 + missingok + notifempty + size 10M + postrotate + /bin/kill -HUP `cat /var/run/rsyslog.pid 2> /dev/null` 2> /dev/null || true + endscript +} diff --git a/patches/systemvm/debian/config/etc/logrotate.d/ppp b/patches/systemvm/debian/config/etc/logrotate.d/ppp new file mode 100644 index 00000000000..35054639a4d --- /dev/null +++ b/patches/systemvm/debian/config/etc/logrotate.d/ppp @@ -0,0 +1,9 @@ +/var/log/ppp-connect-errors { + weekly + rotate 4 + missingok + notifempty + compress + nocreate +} + diff --git a/patches/systemvm/debian/config/etc/logrotate.d/rsyslog b/patches/systemvm/debian/config/etc/logrotate.d/rsyslog new file mode 100644 index 00000000000..9ce5a447a43 --- /dev/null +++ b/patches/systemvm/debian/config/etc/logrotate.d/rsyslog @@ -0,0 +1,37 @@ +/var/log/syslog +{ + rotate 7 + daily + missingok + notifempty + delaycompress + compress + postrotate + invoke-rc.d rsyslog reload > /dev/null + endscript +} + +/var/log/mail.info +/var/log/mail.warn +/var/log/mail.err +/var/log/mail.log +/var/log/daemon.log +/var/log/kern.log +/var/log/auth.log +/var/log/user.log +/var/log/lpr.log +/var/log/cron.log +/var/log/debug +/var/log/messages +{ + rotate 4 + weekly + missingok + notifempty + compress + delaycompress + sharedscripts + postrotate + invoke-rc.d rsyslog reload > /dev/null + endscript +} diff --git a/patches/systemvm/debian/config/etc/rsyslog.conf b/patches/systemvm/debian/config/etc/rsyslog.conf new file mode 100644 index 00000000000..8dc7c7504ca --- /dev/null +++ b/patches/systemvm/debian/config/etc/rsyslog.conf @@ -0,0 +1,106 @@ +# /etc/rsyslog.conf Configuration file for rsyslog. +# +# For more information see +# /usr/share/doc/rsyslog-doc/html/rsyslog_conf.html + + +################# +#### MODULES #### +################# + +$ModLoad imuxsock # provides support for local system logging +$ModLoad imklog # provides kernel logging support (previously done by rklogd) +#$ModLoad immark # provides --MARK-- message capability + +# provides UDP syslog reception +$ModLoad imudp +$UDPServerRun 3914 + +# provides TCP syslog reception +#$ModLoad imtcp +#$InputTCPServerRun 514 + + +########################### +#### GLOBAL DIRECTIVES #### +########################### + +# +# Use traditional timestamp format. +# To enable high precision timestamps, comment out the following line. +# +$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat + +# +# Set the default permissions for all log files. +# +$FileOwner root +$FileGroup adm +$FileCreateMode 0640 +$DirCreateMode 0755 +$Umask 0022 + +# +# Include all config files in /etc/rsyslog.d/ +# +$IncludeConfig /etc/rsyslog.d/*.conf + + +############### +#### RULES #### +############### + +# +# First some standard log files. Log by facility. +# +auth,authpriv.* /var/log/auth.log +#*.*;auth,authpriv.none -/var/log/syslog +cron.* /var/log/cron.log +daemon.* -/var/log/daemon.log +kern.* -/var/log/kern.log +lpr.* -/var/log/lpr.log +mail.* -/var/log/mail.log +#user.* -/var/log/user.log + +# +# Logging for the mail system. Split it up so that +# it is easy to write scripts to parse these files. +# +mail.info -/var/log/mail.info +mail.warn -/var/log/mail.warn +mail.err /var/log/mail.err + +# +# Logging for INN news system. +# +news.crit /var/log/news/news.crit +news.err /var/log/news/news.err +news.notice -/var/log/news/news.notice + +# +# Some "catch-all" log files. +# +#*.=debug;\ +# auth,authpriv.none;\ +# news.none;mail.none -/var/log/debug +*.=info;*.=notice;*.=warn;\ + auth,authpriv.none;\ + cron.none,daemon.none;\ + local0.none,daemon.none;\ + mail.none,news.none -/var/log/messages + +# +# Emergencies are sent to everybody logged in. +# +*.emerg * + +# +# I like to have messages displayed on the console, but only on a virtual +# console I usually leave idle. +# +#daemon,mail.*;\ +# news.=crit;news.=err;news.=notice;\ +# *.=debug;*.=info;\ +# *.=notice;*.=warn /dev/tty8 + +local0.* -/var/log/haproxy.log diff --git a/patches/systemvm/debian/convert.sh b/patches/systemvm/debian/convert.sh index c3fe1c6c13d..3d7dac7f487 100755 --- a/patches/systemvm/debian/convert.sh +++ b/patches/systemvm/debian/convert.sh @@ -17,9 +17,13 @@ bzip2 -c systemvm.qcow2 > systemvm.qcow2.bz2 echo "Done qcow2" echo "Converting raw image to vmdk" qemu-img convert -f raw -O vmdk systemvm.img systemvm.vmdk -echo "Compressing vmdk..." -bzip2 -c systemvm.vmdk > systemvm.vmdk.bz2 -echo "Done vmdk" +echo "Done creating vmdk" +echo "Creating ova appliance " +ovftool systemvm.vmx systemvm.ova +echo "Done creating OVA" +echo "Cleaning up..." +rm -vf systemvm.vmdk +rm -vf systemvm.vhd.bak echo "Compressing raw image..." bzip2 -c systemvm.img > systemvm.img.bz2 @@ -32,5 +36,4 @@ md5sum systemvm.vhd >> md5sum md5sum systemvm.vhd.bz2 >> md5sum md5sum systemvm.qcow2 >> md5sum md5sum systemvm.qcow2.bz2 >> md5sum -md5sum systemvm.vmdk >> md5sum -md5sum systemvm.vmdk.bz2 >> md5sum +md5sum systemvm.ova >> md5sum diff --git a/patches/systemvm/debian/systemvm.vmx b/patches/systemvm/debian/systemvm.vmx new file mode 100644 index 00000000000..9b93449d94a --- /dev/null +++ b/patches/systemvm/debian/systemvm.vmx @@ -0,0 +1,37 @@ +config.version = "8" +displayname = "systemvm" +ethernet0.addressType = "generated" +ethernet0.connectionType = "bridged" +ethernet0.present = "true" +ethernet0.startConnected = "true" +ethernet0.virtualDev = "e1000" +floppy0.autodetect = "false" +floppy0.fileType = "device" +floppy0.present = "true" +floppy0.startConnected = "false" +guestos = "debian5" +ide0:0.deviceType = "disk" +ide0:0.fileName = "systemvm.vmdk" +ide0:0.present = "true" +ide1:0.autodetect = "true" +ide1:0.deviceType = "atapi-cdrom" +ide1:0.present = "true" +ide1:0.startConnected = "false" +memsize = "256" +numvcpus = "1" +pciBridge0.present = "TRUE" +pciBridge4.functions = "8" +pciBridge4.present = "TRUE" +pciBridge4.virtualDev = "pcieRootPort" +pciBridge5.functions = "8" +pciBridge5.present = "TRUE" +pciBridge5.virtualDev = "pcieRootPort" +pciBridge6.functions = "8" +pciBridge6.present = "TRUE" +pciBridge6.virtualDev = "pcieRootPort" +pciBridge7.functions = "8" +pciBridge7.present = "TRUE" +pciBridge7.virtualDev = "pcieRootPort" +svga.autodetect = "true" +virtualhw.version = "7" +vmci0.present = "TRUE" diff --git a/server/src/com/cloud/api/ApiServlet.java b/server/src/com/cloud/api/ApiServlet.java index b8f19b78d2e..b273ccd4fe2 100755 --- a/server/src/com/cloud/api/ApiServlet.java +++ b/server/src/com/cloud/api/ApiServlet.java @@ -263,10 +263,11 @@ public class ApiServlet extends HttpServlet { auditTrailSb.insert(0, "(userId="+UserContext.current().getCallerUserId()+ " accountId="+UserContext.current().getCaller().getId()+ " sessionId="+(session != null ? session.getId() : null)+ ")" ); try { - String response = _apiServer.handleRequest(params, true, responseType, auditTrailSb); + String response = _apiServer.handleRequest(params, false, responseType, auditTrailSb); writeResponse(resp, response != null ? response : "", HttpServletResponse.SC_OK, responseType); } catch (ServerApiException se) { String serializedResponseText = _apiServer.getSerializedApiError(se.getErrorCode(), se.getDescription(), params, responseType); + resp.setHeader("X-Description", se.getDescription()); writeResponse(resp, serializedResponseText, se.getErrorCode(), responseType); auditTrailSb.append(" " +se.getErrorCode() + " " + se.getDescription()); } diff --git a/server/src/com/cloud/api/doc/ApiXmlDocWriter.java b/server/src/com/cloud/api/doc/ApiXmlDocWriter.java index 95ed8945228..c96dae3c6dc 100644 --- a/server/src/com/cloud/api/doc/ApiXmlDocWriter.java +++ b/server/src/com/cloud/api/doc/ApiXmlDocWriter.java @@ -201,10 +201,10 @@ public class ApiXmlDocWriter { regularUserSorted.close(); //gzip directory with xml doc - zipDir(dirName + "xmldoc.zip", xmlDocDir); + //zipDir(dirName + "xmldoc.zip", xmlDocDir); //Delete directory - deleteDir(new File(xmlDocDir)); + //deleteDir(new File(xmlDocDir)); } catch (Exception ex) { ex.printStackTrace(); diff --git a/server/src/com/cloud/configuration/DefaultComponentLibrary.java b/server/src/com/cloud/configuration/DefaultComponentLibrary.java index 5b64912c11b..6ba6c5edf5c 100644 --- a/server/src/com/cloud/configuration/DefaultComponentLibrary.java +++ b/server/src/com/cloud/configuration/DefaultComponentLibrary.java @@ -116,6 +116,7 @@ import com.cloud.storage.upload.UploadMonitorImpl; import com.cloud.template.TemplateManagerImpl; import com.cloud.user.AccountManagerImpl; import com.cloud.user.dao.AccountDaoImpl; +import com.cloud.user.dao.SSHKeyPairDaoImpl; import com.cloud.user.dao.UserAccountDaoImpl; import com.cloud.user.dao.UserDaoImpl; import com.cloud.user.dao.UserStatisticsDaoImpl; @@ -135,6 +136,7 @@ import com.cloud.vm.dao.InstanceGroupVMMapDaoImpl; import com.cloud.vm.dao.NicDaoImpl; import com.cloud.vm.dao.SecondaryStorageVmDaoImpl; import com.cloud.vm.dao.UserVmDaoImpl; +import com.cloud.vm.dao.UserVmDetailsDaoImpl; import com.cloud.vm.dao.VMInstanceDaoImpl; public class DefaultComponentLibrary implements ComponentLibrary { @@ -240,8 +242,10 @@ public class DefaultComponentLibrary implements ComponentLibrary { addDao("ItWorkDao", ItWorkDaoImpl.class); addDao("FirewallRulesDao", FirewallRulesDaoImpl.class); addDao("PortForwardingRulesDao", PortForwardingRulesDaoImpl.class); + addDao("SSHKeyPairDao", SSHKeyPairDaoImpl.class); addDao("UsageEventDao", UsageEventDaoImpl.class); addDao("ClusterDetailsDao", ClusterDetailsDaoImpl.class); + addDao("UserVmDetailsDao", UserVmDetailsDaoImpl.class); addDao("VlanMappingDao", VlanMappingDaoImpl.class); addDao("VlanMappingDirtyDao", VlanMappingDirtyDaoImpl.class); addDao("OvsWorkDao", OvsWorkDaoImpl.class); diff --git a/server/src/com/cloud/network/NetworkManagerImpl.java b/server/src/com/cloud/network/NetworkManagerImpl.java index 4100d407fcd..ffcae31a063 100755 --- a/server/src/com/cloud/network/NetworkManagerImpl.java +++ b/server/src/com/cloud/network/NetworkManagerImpl.java @@ -169,7 +169,6 @@ public class NetworkManagerImpl implements NetworkManager, NetworkService, Manag @Inject LoadBalancingRulesManager _lbMgr; @Inject UsageEventDao _usageEventDao; @Inject PodVlanMapDao _podVlanMapDao; - @Inject(adapter=NetworkGuru.class) Adapters _networkGurus; @Inject(adapter=NetworkElement.class) @@ -1122,7 +1121,7 @@ public class NetworkManagerImpl implements NetworkManager, NetworkService, Manag private Account findAccountByIpAddress(Ip ipAddress) { IPAddressVO address = _ipAddressDao.findById(ipAddress); if ((address != null) && (address.getAllocatedToAccountId() != null)) { - return _accountMgr.getAccount(address.getAllocatedToAccountId()); + return _accountMgr.getActiveAccount(address.getAllocatedToAccountId()); } return null; } @@ -1161,14 +1160,14 @@ public class NetworkManagerImpl implements NetworkManager, NetworkService, Manag return true; } - Account Account = _accountMgr.getAccount(accountId); - if (Account == null) { + Account account = _accountMgr.getAccount(accountId); + if (account == null) { return false; } if ((ipVO.getAllocatedToAccountId() == null) || (ipVO.getAllocatedToAccountId().longValue() != accountId)) { // FIXME: is the user visible in the admin account's domain???? - if (!BaseCmd.isAdmin(Account.getType())) { + if (!BaseCmd.isAdmin(account.getType())) { if (s_logger.isDebugEnabled()) { s_logger.debug("permission denied disassociating IP address " + ipAddress + "; acct: " + accountId + "; ip (acct / dc / dom / alloc): " + ipVO.getAllocatedToAccountId() + " / " + ipVO.getDataCenterId() + " / " + ipVO.getAllocatedInDomainId() + " / " + ipVO.getAllocatedTime()); @@ -1567,8 +1566,7 @@ public class NetworkManagerImpl implements NetworkManager, NetworkService, Manag } } else { Account owner = _accountMgr.getAccount(network.getAccountId()); - Domain domain = _domainDao.findById(owner.getDomainId()); - _accountMgr.checkAccess(caller, domain); + _accountMgr.checkAccess(caller, owner); } //Don't allow to remove network if there are non-destroyed vms using it @@ -1748,7 +1746,7 @@ public class NetworkManagerImpl implements NetworkManager, NetworkService, Manag } } - Account owner = _accountMgr.getAccount(cmd.getEntityOwnerId()); + Account owner = _accountMgr.getActiveAccount(cmd.getEntityOwnerId()); if (!_accountMgr.isAdmin(caller.getType())) { _accountMgr.checkAccess(caller, network); } else { diff --git a/server/src/com/cloud/network/element/VirtualRouterElement.java b/server/src/com/cloud/network/element/VirtualRouterElement.java index 0e59a7363cc..d641635b3b4 100644 --- a/server/src/com/cloud/network/element/VirtualRouterElement.java +++ b/server/src/com/cloud/network/element/VirtualRouterElement.java @@ -32,25 +32,26 @@ import com.cloud.deploy.DeployDestination; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.Host; import com.cloud.network.LoadBalancerVO; import com.cloud.network.Network; -import com.cloud.network.Network.Capability; -import com.cloud.network.Network.GuestIpType; -import com.cloud.network.Network.Provider; -import com.cloud.network.Network.Service; import com.cloud.network.NetworkManager; import com.cloud.network.PublicIpAddress; import com.cloud.network.RemoteAccessVpn; import com.cloud.network.VpnUser; +import com.cloud.network.Network.Capability; +import com.cloud.network.Network.GuestIpType; +import com.cloud.network.Network.Provider; +import com.cloud.network.Network.Service; import com.cloud.network.dao.LoadBalancerDao; import com.cloud.network.dao.NetworkDao; import com.cloud.network.lb.LoadBalancingRule; -import com.cloud.network.lb.LoadBalancingRule.LbDestination; import com.cloud.network.lb.LoadBalancingRulesManager; +import com.cloud.network.lb.LoadBalancingRule.LbDestination; import com.cloud.network.router.VirtualNetworkApplianceManager; import com.cloud.network.rules.FirewallRule; -import com.cloud.network.rules.FirewallRule.Purpose; import com.cloud.network.rules.PortForwardingRule; +import com.cloud.network.rules.FirewallRule.Purpose; import com.cloud.network.vpn.RemoteAccessVpnElement; import com.cloud.offering.NetworkOffering; import com.cloud.offerings.dao.NetworkOfferingDao; @@ -63,8 +64,8 @@ import com.cloud.vm.NicProfile; import com.cloud.vm.ReservationContext; import com.cloud.vm.UserVmManager; import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.UserVmDao; @@ -96,6 +97,7 @@ public class VirtualRouterElement extends AdapterBase implements NetworkElement, if (!canHandle(guestConfig.getGuestType(), dest.getDataCenter())) { return false; } + _routerMgr.deployVirtualRouter(guestConfig, dest, context.getAccount()); return true; @@ -111,6 +113,30 @@ public class VirtualRouterElement extends AdapterBase implements NetworkElement, @SuppressWarnings("unchecked") VirtualMachineProfile uservm = (VirtualMachineProfile)vm; + DomainRouterVO router = _routerDao.findById(uservm.getVirtualMachine().getDomainRouterId()); + if(router != null) { + State state = router.getState(); + if ( state == State.Starting ) { + // wait 300 seconds + for ( int i = 0; i < 300; ) { + try { + Thread.sleep(2000); + } catch (Exception e) { + } + i += 2; + + state = router.getState(); + if ( state != State.Starting ) { + break; + } + } + } + + // TODO: need to find a better exception to throw! + if(state != State.Running) + throw new ResourceUnavailableException("Virtual router is not available", Host.class, router.getHostId()); + } + return _routerMgr.addVirtualMachineIntoNetwork(config, nic, uservm, dest, context, false) != null; } else { return false; diff --git a/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java b/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java index 76e5d2cfb71..6e0366c0f62 100644 --- a/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -72,10 +72,9 @@ import com.cloud.configuration.ConfigurationManager; import com.cloud.configuration.dao.ConfigurationDao; import com.cloud.configuration.dao.ResourceLimitDao; import com.cloud.dc.DataCenter; -import com.cloud.dc.DataCenter.NetworkType; import com.cloud.dc.DataCenterVO; import com.cloud.dc.HostPodVO; -import com.cloud.dc.Vlan; +import com.cloud.dc.DataCenter.NetworkType; import com.cloud.dc.dao.AccountVlanMapDao; import com.cloud.dc.dao.DataCenterDao; import com.cloud.dc.dao.HostPodDao; @@ -105,18 +104,18 @@ import com.cloud.network.IPAddressVO; import com.cloud.network.IpAddress; import com.cloud.network.LoadBalancerVO; import com.cloud.network.Network; -import com.cloud.network.Network.GuestIpType; import com.cloud.network.NetworkManager; import com.cloud.network.NetworkVO; -import com.cloud.network.Networks.BroadcastDomainType; -import com.cloud.network.Networks.IsolationType; -import com.cloud.network.Networks.TrafficType; import com.cloud.network.PublicIpAddress; import com.cloud.network.RemoteAccessVpn; import com.cloud.network.SshKeysDistriMonitor; import com.cloud.network.VirtualNetworkApplianceService; import com.cloud.network.VpnUser; import com.cloud.network.VpnUserVO; +import com.cloud.network.Network.GuestIpType; +import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.Networks.IsolationType; +import com.cloud.network.Networks.TrafficType; import com.cloud.network.addr.PublicIp; import com.cloud.network.dao.FirewallRulesDao; import com.cloud.network.dao.IPAddressDao; @@ -127,8 +126,8 @@ import com.cloud.network.dao.NetworkRuleConfigDao; import com.cloud.network.dao.RemoteAccessVpnDao; import com.cloud.network.dao.VpnUserDao; import com.cloud.network.lb.LoadBalancingRule; -import com.cloud.network.lb.LoadBalancingRule.LbDestination; import com.cloud.network.lb.LoadBalancingRulesManager; +import com.cloud.network.lb.LoadBalancingRule.LbDestination; import com.cloud.network.ovs.GreTunnelException; import com.cloud.network.ovs.OvsNetworkManager; import com.cloud.network.router.VirtualRouter.Role; @@ -177,11 +176,11 @@ import com.cloud.vm.ReservationContext; import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.VirtualMachineGuru; import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.VirtualMachineName; import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.UserVmDao; @@ -887,7 +886,7 @@ public class VirtualNetworkApplianceManagerImpl implements VirtualNetworkApplian } private VmDataCommand generateVmDataCommand(VirtualRouter router, String vmPrivateIpAddress, - String userData, String serviceOffering, String zoneName, String guestIpAddress, String vmName, String vmInstanceName, long vmId) { + String userData, String serviceOffering, String zoneName, String guestIpAddress, String vmName, String vmInstanceName, long vmId, String publicKey) { VmDataCommand cmd = new VmDataCommand(vmPrivateIpAddress); cmd.setAccessDetail(NetworkElementCommand.ROUTER_IP, router.getPrivateIpAddress()); @@ -902,6 +901,7 @@ public class VirtualNetworkApplianceManagerImpl implements VirtualNetworkApplian cmd.addVmData("metadata", "public-hostname", router.getPublicIpAddress()); cmd.addVmData("metadata", "instance-id", vmInstanceName); cmd.addVmData("metadata", "vm-id", String.valueOf(vmId)); + cmd.addVmData("metadata", "public-keys", publicKey); return cmd; } @@ -1033,24 +1033,9 @@ public class VirtualNetworkApplianceManagerImpl implements VirtualNetworkApplian } else { EventUtils.saveEvent(User.UID_SYSTEM, owner.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_ROUTER_CREATE, "router creation failed", startEventId); } - - } - State state = router.getState(); - - if ( state == State.Starting ) { - // wait 300 seconds - for ( int i = 0; i < 300; ) { - try { - Thread.sleep(2); - } catch (Exception e) { - } - i += 2; - state = router.getState(); - if ( state != State.Starting ) { - break; - } - } } + + State state = router.getState(); if (state != State.Starting && state != State.Running) { long startEventId = EventUtils.saveStartedEvent(User.UID_SYSTEM, owner.getId(), EventTypes.EVENT_ROUTER_START, "Starting router : " +router.getName()); router = this.start(router, _accountService.getSystemUser(), _accountService.getSystemAccount()); @@ -1060,11 +1045,8 @@ public class VirtualNetworkApplianceManagerImpl implements VirtualNetworkApplian EventUtils.saveEvent(User.UID_SYSTEM, owner.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_ROUTER_START, "failed to start router", startEventId); } } - state = router.getState(); - if ( state == State.Running ) { - return router; - } - throw new CloudRuntimeException(router.getName() + " is not running , it is in " + state); + + return router; } @Override @@ -1437,8 +1419,11 @@ public class VirtualNetworkApplianceManagerImpl implements VirtualNetworkApplian VirtualRouter router = startDhcp ? deployDhcp(network, dest, profile.getOwner()) : deployVirtualRouter(network, dest, profile.getOwner()); + _userVmDao.loadDetails((UserVmVO) profile.getVirtualMachine()); + String password = profile.getVirtualMachine().getPassword(); String userData = profile.getVirtualMachine().getUserData(); + String sshPublicKey = profile.getVirtualMachine().getDetail("SSH.PublicKey"); Commands cmds = new Commands(OnError.Stop); String routerControlIpAddress = null; @@ -1468,7 +1453,7 @@ public class VirtualNetworkApplianceManagerImpl implements VirtualNetworkApplian cmds.addCommand( "vmdata", generateVmDataCommand(router, nic.getIp4Address(), userData, serviceOffering, zoneName, - nic.getIp4Address(), profile.getVirtualMachine().getName(), profile.getVirtualMachine().getInstanceName(), profile.getId())); + nic.getIp4Address(), profile.getVirtualMachine().getName(), profile.getVirtualMachine().getInstanceName(), profile.getId(), sshPublicKey)); try { _agentMgr.send(router.getHostId(), cmds); @@ -1696,7 +1681,7 @@ public class VirtualNetworkApplianceManagerImpl implements VirtualNetworkApplian cmds.addCommand( "vmdata", generateVmDataCommand(router, nic.getIp4Address(), vm.getUserData(), serviceOffering, zoneName, - nic.getIp4Address(), vm.getName(), vm.getInstanceName(), vm.getId())); + nic.getIp4Address(), vm.getName(), vm.getInstanceName(), vm.getId(), null)); } } } diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index 8c042489443..0ec6d3b4642 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -75,10 +75,13 @@ import com.cloud.api.ApiDBUtils; import com.cloud.api.BaseCmd; import com.cloud.api.ServerApiException; import com.cloud.api.commands.CreateDomainCmd; +import com.cloud.api.commands.CreateSSHKeyPairCmd; import com.cloud.api.commands.DeleteDomainCmd; import com.cloud.api.commands.DeletePreallocatedLunCmd; +import com.cloud.api.commands.DeleteSSHKeyPairCmd; import com.cloud.api.commands.ExtractVolumeCmd; import com.cloud.api.commands.GetCloudIdentifierCmd; +import com.cloud.api.commands.GetVMPasswordCmd; import com.cloud.api.commands.ListAccountsCmd; import com.cloud.api.commands.ListAlertsCmd; import com.cloud.api.commands.ListAsyncJobsCmd; @@ -99,6 +102,7 @@ import com.cloud.api.commands.ListPodsByCmd; import com.cloud.api.commands.ListPreallocatedLunsCmd; import com.cloud.api.commands.ListPublicIpAddressesCmd; import com.cloud.api.commands.ListRoutersCmd; +import com.cloud.api.commands.ListSSHKeyPairsCmd; import com.cloud.api.commands.ListServiceOfferingsCmd; import com.cloud.api.commands.ListStoragePoolsCmd; import com.cloud.api.commands.ListSystemVMsCmd; @@ -112,6 +116,7 @@ import com.cloud.api.commands.ListZonesByCmd; import com.cloud.api.commands.RebootSystemVmCmd; import com.cloud.api.commands.RegisterCmd; import com.cloud.api.commands.RegisterPreallocatedLunCmd; +import com.cloud.api.commands.RegisterSSHKeyPairCmd; import com.cloud.api.commands.StartSystemVMCmd; import com.cloud.api.commands.StopSystemVmCmd; import com.cloud.api.commands.UpdateDomainCmd; @@ -222,12 +227,15 @@ import com.cloud.template.VirtualMachineTemplate.TemplateFilter; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.AccountVO; +import com.cloud.user.SSHKeyPair; +import com.cloud.user.SSHKeyPairVO; import com.cloud.user.User; import com.cloud.user.UserAccount; import com.cloud.user.UserAccountVO; import com.cloud.user.UserContext; import com.cloud.user.UserVO; import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.SSHKeyPairDao; import com.cloud.user.dao.UserAccountDao; import com.cloud.user.dao.UserDao; import com.cloud.utils.EnumUtils; @@ -249,10 +257,12 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.ExecutionException; import com.cloud.utils.net.MacAddress; import com.cloud.utils.net.NetUtils; +import com.cloud.utils.ssh.SSHKeysHelper; import com.cloud.vm.ConsoleProxyVO; import com.cloud.vm.DomainRouterVO; import com.cloud.vm.InstanceGroupVO; import com.cloud.vm.SecondaryStorageVmVO; +import com.cloud.vm.UserVmDetailVO; import com.cloud.vm.UserVmManager; import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; @@ -263,6 +273,7 @@ import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.InstanceGroupDao; import com.cloud.vm.dao.SecondaryStorageVmDao; import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.VMInstanceDao; public class ManagementServerImpl implements ManagementServer { @@ -320,6 +331,8 @@ public class ManagementServerImpl implements ManagementServer { private final UploadMonitor _uploadMonitor; private final UploadDao _uploadDao; private final CertificateDao _certDao; + private final SSHKeyPairDao _sshKeyPairDao; + private final ScheduledExecutorService _eventExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("EventChecker")); @@ -391,6 +404,7 @@ public class ManagementServerImpl implements ManagementServer { _asyncMgr = locator.getManager(AsyncJobManager.class); _tmpltMgr = locator.getManager(TemplateManager.class); _uploadMonitor = locator.getManager(UploadMonitor.class); + _sshKeyPairDao = locator.getDao(SSHKeyPairDao.class); _userAuthenticators = locator.getAdapters(UserAuthenticator.class); if (_userAuthenticators == null || !_userAuthenticators.isSet()) { @@ -4663,4 +4677,93 @@ public class ManagementServerImpl implements ManagementServer { } return _hashKey; } + + @Override + public SSHKeyPair createSSHKeyPair(CreateSSHKeyPairCmd cmd) { + Account account = UserContext.current().getCaller(); + SSHKeyPairVO s = _sshKeyPairDao.findByName(account.getAccountId(), account.getDomainId(), cmd.getName()); + if (s != null) + throw new InvalidParameterValueException("A key pair with name '" + cmd.getName() + "' already exists."); + + SSHKeysHelper keys = new SSHKeysHelper(); + + String name = cmd.getName(); + String publicKey = keys.getPublicKey(); + String fingerprint = keys.getPublicKeyFingerPrint(); + String privateKey = keys.getPrivateKey(); + + return createAndSaveSSHKeyPair(name, fingerprint, publicKey, privateKey); + } + + @Override + public boolean deleteSSHKeyPair(DeleteSSHKeyPairCmd cmd) { + Account account = UserContext.current().getCaller(); + SSHKeyPairVO s = _sshKeyPairDao.findByName(account.getAccountId(), account.getDomainId(), cmd.getName()); + if (s == null) + throw new InvalidParameterValueException("A key pair with name '" + cmd.getName() + "' does not exist."); + + return _sshKeyPairDao.deleteByName(account.getAccountId(), account.getDomainId(), cmd.getName()); + } + + @Override + public List listSSHKeyPairs(ListSSHKeyPairsCmd cmd) { + Account account = UserContext.current().getCaller(); + + if (cmd.getName() != null && cmd.getName().length() > 0) + return _sshKeyPairDao.listKeyPairsByName(account.getAccountId(), account.getDomainId(), cmd.getName()); + + if (cmd.getFingerprint() != null && cmd.getFingerprint().length() > 0) + return _sshKeyPairDao.listKeyPairsByFingerprint(account.getAccountId(), account.getDomainId(), cmd.getFingerprint()); + + return _sshKeyPairDao.listKeyPairs(account.getAccountId(), account.getDomainId()); + } + + @Override + public SSHKeyPair registerSSHKeyPair(RegisterSSHKeyPairCmd cmd) { + Account account = UserContext.current().getCaller(); + SSHKeyPairVO s = _sshKeyPairDao.findByName(account.getAccountId(), account.getDomainId(), cmd.getName()); + if (s != null) + throw new InvalidParameterValueException("A key pair with name '" + cmd.getName() + "' already exists."); + + String name = cmd.getName(); + String publicKey = SSHKeysHelper.getPublicKeyFromKeyMaterial(cmd.getPublicKey()); + String fingerprint = SSHKeysHelper.getPublicKeyFingerprint(publicKey); + + if (publicKey == null) + throw new InvalidParameterValueException("Public key is invalid"); + + return createAndSaveSSHKeyPair(name, fingerprint, publicKey, null); + } + + private SSHKeyPair createAndSaveSSHKeyPair(String name, String fingerprint, String publicKey, String privateKey) { + Account account = UserContext.current().getCaller(); + SSHKeyPairVO newPair = new SSHKeyPairVO(); + + newPair.setAccountId(account.getAccountId()); + newPair.setDomainId(account.getDomainId()); + newPair.setName(name); + newPair.setFingerprint(fingerprint); + newPair.setPublicKey(publicKey); + newPair.setPrivateKey(privateKey); // transient; not saved. + + _sshKeyPairDao.persist(newPair); + + return newPair; + } + + @Override + public String getVMPassword(GetVMPasswordCmd cmd) { + Account account = UserContext.current().getCaller(); + UserVmVO vm = _userVmDao.findById(cmd.getId()); + if (vm == null || vm.getAccountId() != account.getAccountId()) + throw new InvalidParameterValueException("No VM with id '" + cmd.getId() + "' found."); + + _userVmDao.loadDetails(vm); + String password = vm.getDetail("Encrypted.Password"); + if (password == null || password.equals("")) + throw new InvalidParameterValueException("No password for VM with id '" + cmd.getId() + "' found."); + + return password; + } + } diff --git a/server/src/com/cloud/user/AccountManagerImpl.java b/server/src/com/cloud/user/AccountManagerImpl.java index f8dfe283141..f0dbd1f403f 100755 --- a/server/src/com/cloud/user/AccountManagerImpl.java +++ b/server/src/com/cloud/user/AccountManagerImpl.java @@ -1613,12 +1613,22 @@ public class AccountManagerImpl implements AccountManager, AccountService, Manag } } + @Override - public Account getAccount(Long accountId) { + public Account getActiveAccount(Long accountId) { if (accountId == null) { throw new InvalidParameterValueException("AccountId is required by account search"); } else { return _accountDao.findById(accountId); } } + + @Override + public Account getAccount(Long accountId) { + if (accountId == null) { + throw new InvalidParameterValueException("AccountId is required by account search"); + } else { + return _accountDao.findByIdIncludingRemoved(accountId); + } + } } diff --git a/server/src/com/cloud/user/dao/SSHKeyPairDao.java b/server/src/com/cloud/user/dao/SSHKeyPairDao.java new file mode 100644 index 00000000000..7f7aece27d2 --- /dev/null +++ b/server/src/com/cloud/user/dao/SSHKeyPairDao.java @@ -0,0 +1,20 @@ +package com.cloud.user.dao; + +import java.util.List; + +import com.cloud.user.SSHKeyPairVO; +import com.cloud.utils.db.GenericDao; + +public interface SSHKeyPairDao extends GenericDao { + + public List listKeyPairs(long accountId, long domainId); + + public List listKeyPairsByName(long accountId, long domainId, String name); + + public List listKeyPairsByFingerprint(long accountId, long domainId, String fingerprint); + + public SSHKeyPairVO findByName(long accountId, long domainId, String name); + + public boolean deleteByName(long accountId, long domainId, String name); + +} diff --git a/server/src/com/cloud/user/dao/SSHKeyPairDaoImpl.java b/server/src/com/cloud/user/dao/SSHKeyPairDaoImpl.java new file mode 100644 index 00000000000..1f2ff899e70 --- /dev/null +++ b/server/src/com/cloud/user/dao/SSHKeyPairDaoImpl.java @@ -0,0 +1,59 @@ +package com.cloud.user.dao; + +import java.util.List; + +import javax.ejb.Local; + +import com.cloud.user.SSHKeyPairVO; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchCriteria; + +@Local(value={SSHKeyPairDao.class}) +public class SSHKeyPairDaoImpl extends GenericDaoBase implements SSHKeyPairDao { + + @Override + public List listKeyPairs(long accountId, long domainId) { + SearchCriteria sc = createSearchCriteria(); + sc.addAnd("accountId", SearchCriteria.Op.EQ, accountId); + sc.addAnd("domainId", SearchCriteria.Op.EQ, domainId); + return listBy(sc); + } + + @Override + public List listKeyPairsByName(long accountId, long domainId, String name) { + SearchCriteria sc = createSearchCriteria(); + sc.addAnd("accountId", SearchCriteria.Op.EQ, accountId); + sc.addAnd("domainId", SearchCriteria.Op.EQ, domainId); + sc.addAnd("name", SearchCriteria.Op.EQ, name); + return listBy(sc); + } + + @Override + public List listKeyPairsByFingerprint(long accountId, long domainId, String fingerprint) { + SearchCriteria sc = createSearchCriteria(); + sc.addAnd("accountId", SearchCriteria.Op.EQ, accountId); + sc.addAnd("domainId", SearchCriteria.Op.EQ, domainId); + sc.addAnd("fingerprint", SearchCriteria.Op.EQ, fingerprint); + return listBy(sc); + } + + @Override + public SSHKeyPairVO findByName(long accountId, long domainId, String name) { + SearchCriteria sc = createSearchCriteria(); + sc.addAnd("accountId", SearchCriteria.Op.EQ, accountId); + sc.addAnd("domainId", SearchCriteria.Op.EQ, domainId); + sc.addAnd("name", SearchCriteria.Op.EQ, name); + return findOneBy(sc); + } + + @Override + public boolean deleteByName(long accountId, long domainId, String name) { + SSHKeyPairVO pair = findByName(accountId, domainId, name); + if (pair == null) + return false; + + expunge(pair.getId()); + return true; + } + +} diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 6c8dd565099..064b36c3dee 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -170,10 +170,12 @@ import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.AccountService; import com.cloud.user.AccountVO; +import com.cloud.user.SSHKeyPair; import com.cloud.user.User; import com.cloud.user.UserContext; import com.cloud.user.UserVO; import com.cloud.user.dao.AccountDao; +import com.cloud.user.dao.SSHKeyPairDao; import com.cloud.user.dao.UserDao; import com.cloud.user.dao.UserStatisticsDao; import com.cloud.uservm.UserVm; @@ -185,6 +187,7 @@ import com.cloud.utils.component.ComponentLocator; import com.cloud.utils.component.Inject; import com.cloud.utils.component.Manager; import com.cloud.utils.concurrency.NamedThreadFactory; +import com.cloud.utils.crypt.RSAHelper; import com.cloud.utils.db.DB; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GlobalLock; @@ -203,6 +206,7 @@ import com.cloud.vm.dao.InstanceGroupDao; import com.cloud.vm.dao.InstanceGroupVMMapDao; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.UserVmDetailsDao; @Local(value={UserVmManager.class, UserVmService.class}) public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager { private static final Logger s_logger = Logger.getLogger(UserVmManagerImpl.class); @@ -259,6 +263,8 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager @Inject RulesManager _rulesMgr; @Inject LoadBalancingRulesManager _lbMgr; @Inject UsageEventDao _usageEventDao; + @Inject SSHKeyPairDao _sshKeyPairDao; + @Inject UserVmDetailsDao _vmDetailsDao; @Inject OvsNetworkManager _ovsNetworkMgr; private IpAddrAllocator _IpAllocator; @@ -2222,6 +2228,17 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager } } + // Find an SSH public key corresponding to the key pair name, if one is given + String sshPublicKey = null; + if (cmd.getSSHKeyPairName() != null && !cmd.getSSHKeyPairName().equals("")) { + Account account = UserContext.current().getCaller(); + SSHKeyPair pair = _sshKeyPairDao.findByName(account.getAccountId(), account.getDomainId(), cmd.getSSHKeyPairName()); + if (pair == null) + throw new InvalidParameterValueException("A key pair with name '" + cmd.getSSHKeyPairName() + "' was not found."); + + sshPublicKey = pair.getPublicKey(); + } + _accountMgr.checkAccess(caller, template); DataCenterDeployment plan = new DataCenterDeployment(dc.getId()); @@ -2299,11 +2316,15 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager UserVmVO vm = new UserVmVO(id, instanceName, cmd.getDisplayName(), template.getId(), hypervisorType, template.getGuestOSId(), offering.getOfferHA(), domainId, owner.getId(), offering.getId(), userData, hostName); + if (sshPublicKey != null) + vm.setDetail("SSH.PublicKey", sshPublicKey); if (_itMgr.allocate(vm, template, offering, rootDiskOffering, dataDiskOfferings, networks, null, plan, cmd.getHypervisor(), owner) == null) { return null; } + _vmDao.saveDetails(vm); + if (s_logger.isDebugEnabled()) { s_logger.debug("Successfully allocated DB entry for " + vm); } @@ -2331,6 +2352,7 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager public UserVm startVirtualMachine(DeployVMCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ConcurrentOperationException { long vmId = cmd.getEntityId(); UserVmVO vm = _vmDao.findById(vmId); + _vmDao.loadDetails(vm); // Check that the password was passed in and is valid VMTemplateVO template = _templateDao.findById(vm.getTemplateId()); @@ -2344,6 +2366,17 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager throw new InvalidParameterValueException("A valid password for this virtual machine was not provided."); } vm.setPassword(password); + + // Check if an SSH key pair was selected for the instance and if so use it to encrypt & save the vm password + String sshPublicKey = vm.getDetail("SSH.PublicKey"); + if (sshPublicKey != null && !sshPublicKey.equals("") && password != null && !password.equals("saved_password") ) { + String encryptedPasswd = RSAHelper.encryptWithSSHPublicKey(sshPublicKey, password); + if (encryptedPasswd == null) + throw new ServerApiException(BaseCmd.INTERNAL_ERROR, "Error encrypting password"); + + vm.setDetail("Encrypted.Password", encryptedPasswd); + _vmDao.saveDetails(vm); + } long userId = UserContext.current().getCallerUserId(); UserVO caller = _userDao.findById(userId); diff --git a/server/src/com/cloud/vm/dao/UserVmDao.java b/server/src/com/cloud/vm/dao/UserVmDao.java index d372d9bd811..f40f52d5708 100755 --- a/server/src/com/cloud/vm/dao/UserVmDao.java +++ b/server/src/com/cloud/vm/dao/UserVmDao.java @@ -101,4 +101,9 @@ public interface UserVmDao extends GenericDao, StateDao listByAccountIdAndHostId(long accountId, long hostId); + + void loadDetails(UserVmVO vm); + + void saveDetails(UserVmVO vm); + } diff --git a/server/src/com/cloud/vm/dao/UserVmDaoImpl.java b/server/src/com/cloud/vm/dao/UserVmDaoImpl.java index 73e439ded71..c3fcd3c9d39 100755 --- a/server/src/com/cloud/vm/dao/UserVmDaoImpl.java +++ b/server/src/com/cloud/vm/dao/UserVmDaoImpl.java @@ -19,6 +19,7 @@ package com.cloud.vm.dao; import java.util.Date; import java.util.List; +import java.util.Map; import javax.ejb.Local; @@ -61,8 +62,11 @@ public class UserVmDaoImpl extends GenericDaoBase implements Use protected final SearchBuilder DestroySearch; protected SearchBuilder AccountDataCenterVirtualSearch; + protected SearchBuilder UserVmSearch; protected final Attribute _updateTimeAttr; + protected final UserVmDetailsDaoImpl _detailsDao = ComponentLocator.inject(UserVmDetailsDaoImpl.class); + protected UserVmDaoImpl() { AccountSearch = createSearchBuilder(); AccountSearch.and("account", AccountSearch.entity().getAccountId(), SearchCriteria.Op.EQ); @@ -320,18 +324,18 @@ public class UserVmDaoImpl extends GenericDaoBase implements Use @Override public List listByNetworkId(long networkId) { - if (AccountDataCenterVirtualSearch == null) { + if (UserVmSearch == null) { NicDao _nicDao = ComponentLocator.getLocator("management-server").getDao(NicDao.class); SearchBuilder nicSearch = _nicDao.createSearchBuilder(); nicSearch.and("networkId", nicSearch.entity().getNetworkId(), SearchCriteria.Op.EQ); nicSearch.and("ip4Address", nicSearch.entity().getIp4Address(), SearchCriteria.Op.NNULL); - AccountDataCenterVirtualSearch = createSearchBuilder(); - AccountDataCenterVirtualSearch.join("nicSearch", nicSearch, AccountDataCenterVirtualSearch.entity().getId(), nicSearch.entity().getInstanceId(), JoinBuilder.JoinType.INNER); - AccountDataCenterVirtualSearch.done(); + UserVmSearch = createSearchBuilder(); + UserVmSearch.join("nicSearch", nicSearch, UserVmSearch.entity().getId(), nicSearch.entity().getInstanceId(), JoinBuilder.JoinType.INNER); + UserVmSearch.done(); } - SearchCriteria sc = AccountDataCenterVirtualSearch.create(); + SearchCriteria sc = UserVmSearch.create(); sc.setJoinParameters("nicSearch", "networkId", networkId); return listBy(sc); @@ -415,4 +419,19 @@ public class UserVmDaoImpl extends GenericDaoBase implements Use sc.setParameters("accountId", accountId); return listBy(sc); } + + @Override + public void loadDetails(UserVmVO vm) { + Map details = _detailsDao.findDetails(vm.getId()); + vm.setDetails(details); + } + + @Override + public void saveDetails(UserVmVO vm) { + Map details = vm.getDetails(); + if (details == null) { + return; + } + _detailsDao.persist(vm.getId(), details); + } } diff --git a/server/src/com/cloud/vm/dao/UserVmDetailsDao.java b/server/src/com/cloud/vm/dao/UserVmDetailsDao.java new file mode 100644 index 00000000000..f3bbd9e51a6 --- /dev/null +++ b/server/src/com/cloud/vm/dao/UserVmDetailsDao.java @@ -0,0 +1,16 @@ +package com.cloud.vm.dao; + +import java.util.Map; + +import com.cloud.utils.db.GenericDao; +import com.cloud.vm.UserVmDetailVO; + +public interface UserVmDetailsDao extends GenericDao { + Map findDetails(long vmId); + + void persist(long vmId, Map details); + + UserVmDetailVO findDetail(long vmId, String name); + + void deleteDetails(long vmId); +} diff --git a/server/src/com/cloud/vm/dao/UserVmDetailsDaoImpl.java b/server/src/com/cloud/vm/dao/UserVmDetailsDaoImpl.java new file mode 100644 index 00000000000..d9ff2b1a4b3 --- /dev/null +++ b/server/src/com/cloud/vm/dao/UserVmDetailsDaoImpl.java @@ -0,0 +1,80 @@ +package com.cloud.vm.dao; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ejb.Local; + +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.vm.UserVmDetailVO; + +@Local(value=UserVmDetailsDao.class) +public class UserVmDetailsDaoImpl extends GenericDaoBase implements UserVmDetailsDao { + protected final SearchBuilder VmSearch; + protected final SearchBuilder DetailSearch; + + protected UserVmDetailsDaoImpl() { + VmSearch = createSearchBuilder(); + VmSearch.and("vmId", VmSearch.entity().getVmId(), SearchCriteria.Op.EQ); + VmSearch.done(); + + DetailSearch = createSearchBuilder(); + DetailSearch.and("hostId", DetailSearch.entity().getVmId(), SearchCriteria.Op.EQ); + DetailSearch.and("name", DetailSearch.entity().getName(), SearchCriteria.Op.EQ); + DetailSearch.done(); + } + + @Override + public void deleteDetails(long vmId) { + SearchCriteria sc = VmSearch.create(); + sc.setParameters("vmId", vmId); + + List results = search(sc, null); + for (UserVmDetailVO result : results) { + remove(result.getId()); + } + } + + @Override + public UserVmDetailVO findDetail(long vmId, String name) { + SearchCriteria sc = DetailSearch.create(); + sc.setParameters("vmId", vmId); + sc.setParameters("name", name); + + return findOneBy(sc); + } + + @Override + public Map findDetails(long vmId) { + SearchCriteria sc = VmSearch.create(); + sc.setParameters("vmId", vmId); + + List results = search(sc, null); + Map details = new HashMap(results.size()); + for (UserVmDetailVO result : results) { + details.put(result.getName(), result.getValue()); + } + + return details; + } + + @Override + public void persist(long vmId, Map details) { + Transaction txn = Transaction.currentTxn(); + txn.start(); + SearchCriteria sc = VmSearch.create(); + sc.setParameters("vmId", vmId); + expunge(sc); + + for (Map.Entry detail : details.entrySet()) { + UserVmDetailVO vo = new UserVmDetailVO(vmId, detail.getKey(), detail.getValue()); + persist(vo); + } + txn.commit(); + } + +} diff --git a/setup/db/create-index-fk.sql b/setup/db/create-index-fk.sql index 921618f025c..ef3868c213b 100755 --- a/setup/db/create-index-fk.sql +++ b/setup/db/create-index-fk.sql @@ -105,6 +105,8 @@ ALTER TABLE `cloud`.`user_vm` ADD INDEX `i_user_vm__external_ip_address`(`extern ALTER TABLE `cloud`.`user_vm` ADD CONSTRAINT `fk_user_vm__external_vlan_db_id` FOREIGN KEY `fk_user_vm__external_vlan_db_id` (`external_vlan_db_id`) REFERENCES `vlan` (`id`); ALTER TABLE `cloud`.`user_vm` ADD INDEX `i_user_vm__external_vlan_db_id`(`external_vlan_db_id`); +ALTER TABLE `cloud`.`user_vm_details` ADD CONSTRAINT `fk_user_vm_details__vm_id` FOREIGN KEY `fk_user_vm_details__vm_id`(`vm_id`) REFERENCES `vm_instance`(`id`) ON DELETE CASCADE; + #ALTER TABLE `cloud`.`domain_router` ADD CONSTRAINT `fk_domain_router__public_ip_address` FOREIGN KEY `fk_domain_router__public_ip_address` (`public_ip_address`) REFERENCES `user_ip_address` (`public_ip_address`); ALTER TABLE `cloud`.`domain_router` ADD INDEX `i_domain_router__public_ip_address`(`public_ip_address`); ALTER TABLE `cloud`.`domain_router` ADD CONSTRAINT `fk_domain_router__id` FOREIGN KEY `fk_domain_router__id` (`id`) REFERENCES `vm_instance`(`id`) ON DELETE CASCADE; diff --git a/setup/db/create-schema.sql b/setup/db/create-schema.sql index eb88bf6388e..abe5efe9537 100755 --- a/setup/db/create-schema.sql +++ b/setup/db/create-schema.sql @@ -779,11 +779,18 @@ CREATE TABLE `cloud`.`user_vm` ( `external_mac_address` varchar(17) COMMENT 'mac address within the external network', `external_vlan_db_id` bigint unsigned COMMENT 'foreign key into vlan table', `user_data` varchar(2048), - `encrypted_password` varchar(1024) COMMENT 'vm password encrypted with the public key referenced in ssh_keypair', - `ssh_keypair_id` bigint unsigned COMMENT 'id of the ssh keypair used to access the vm and/or encrypt the password', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE `cloud`.`user_vm_details` ( + `id` bigint unsigned NOT NULL auto_increment, + `vm_id` bigint unsigned NOT NULL COMMENT 'vm id', + `name` varchar(255) NOT NULL, + `value` varchar(1024) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + CREATE TABLE `cloud`.`domain_router` ( `id` bigint unsigned UNIQUE NOT NULL COMMENT 'Primary Key', `gateway` varchar(15) COMMENT 'ip address of the gateway to this domR', diff --git a/ui/jsp/instance.jsp b/ui/jsp/instance.jsp index 2442cd37fce..0695126ca78 100644 --- a/ui/jsp/instance.jsp +++ b/ui/jsp/instance.jsp @@ -356,7 +356,7 @@