From f82dec999d89de7085239ba94085b9e34c347af3 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 24 Feb 2011 14:07:10 -0800 Subject: [PATCH] Bug 8208 - bare metal provisioning complete createVirtualMachine method --- api/src/com/cloud/api/BaseCmd.java | 3 + .../com/cloud/api/commands/DeployVMCmd.java | 16 +- api/src/com/cloud/storage/Storage.java | 3 +- api/src/com/cloud/vm/BareMetalVmService.java | 4 + .../DefaultComponentLibrary.java | 3 + .../cloud/template/TemplateManagerImpl.java | 40 +-- .../src/com/cloud/vm/BareMetalVmManager.java | 5 + .../com/cloud/vm/BareMetalVmManagerImpl.java | 280 ++++++++++++++++++ .../src/com/cloud/vm/UserVmManagerImpl.java | 2 +- .../cloud/vm/VirtualMachineManagerImpl.java | 4 +- 10 files changed, 337 insertions(+), 23 deletions(-) create mode 100644 api/src/com/cloud/vm/BareMetalVmService.java create mode 100644 server/src/com/cloud/vm/BareMetalVmManager.java create mode 100644 server/src/com/cloud/vm/BareMetalVmManagerImpl.java diff --git a/api/src/com/cloud/api/BaseCmd.java b/api/src/com/cloud/api/BaseCmd.java index 26fc9adb01c..81bfd9092c6 100755 --- a/api/src/com/cloud/api/BaseCmd.java +++ b/api/src/com/cloud/api/BaseCmd.java @@ -53,6 +53,7 @@ import com.cloud.user.UserContext; import com.cloud.utils.Pair; import com.cloud.utils.component.ComponentLocator; import com.cloud.vm.UserVmService; +import com.cloud.vm.BareMetalVmService; public abstract class BaseCmd { private static final Logger s_logger = Logger.getLogger(BaseCmd.class.getName()); @@ -109,6 +110,7 @@ public abstract class BaseCmd { public static RulesService _rulesService; public static LoadBalancingRulesService _lbService; public static RemoteAccessVpnService _ravService; + public static BareMetalVmService _bareMetalVmService; static void setComponents(ResponseGenerator generator) { @@ -130,6 +132,7 @@ public abstract class BaseCmd { _lbService = locator.getManager(LoadBalancingRulesService.class); _ravService = locator.getManager(RemoteAccessVpnService.class); _responseGenerator = generator; + _bareMetalVmService = locator.getManager(BareMetalVmService.class); } public abstract void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException; diff --git a/api/src/com/cloud/api/commands/DeployVMCmd.java b/api/src/com/cloud/api/commands/DeployVMCmd.java index 661bceab051..cd7bc5c1b9b 100644 --- a/api/src/com/cloud/api/commands/DeployVMCmd.java +++ b/api/src/com/cloud/api/commands/DeployVMCmd.java @@ -35,6 +35,7 @@ import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.user.Account; import com.cloud.user.UserContext; @@ -228,7 +229,12 @@ public class DeployVMCmd extends BaseAsyncCreateCmd { UserVm result; try { UserContext.current().setEventDetails("Vm Id: "+getEntityId()); - result = _userVmService.startVirtualMachine(this); + if (getHypervisor() == HypervisorType.BareMetal) { + result = _bareMetalVmService.startVirtualMachine(this); + } else { + result = _userVmService.startVirtualMachine(this); + } + if (result != null) { UserVmResponse response = _responseGenerator.createUserVmResponse(result); response.setResponseName(getCommandName()); @@ -252,7 +258,13 @@ public class DeployVMCmd extends BaseAsyncCreateCmd { @Override public void create() throws ResourceAllocationException{ try { - UserVm result = _userVmService.createVirtualMachine(this); + UserVm result; + if (getHypervisor() == HypervisorType.BareMetal) { + result = _bareMetalVmService.createVirtualMachine(this); + } else { + result = _userVmService.createVirtualMachine(this); + } + if (result != null){ setEntityId(result.getId()); } else { diff --git a/api/src/com/cloud/storage/Storage.java b/api/src/com/cloud/storage/Storage.java index 5e5adb27b48..66b4c1c0a8e 100644 --- a/api/src/com/cloud/storage/Storage.java +++ b/api/src/com/cloud/storage/Storage.java @@ -23,7 +23,8 @@ public class Storage { RAW(false, false, false), VHD(true, true, true), ISO(false, false, false), - OVA(true, true, true, "ova"); + OVA(true, true, true, "ova"), + BAREMETAL(false, false, false); private final boolean thinProvisioned; private final boolean supportSparse; diff --git a/api/src/com/cloud/vm/BareMetalVmService.java b/api/src/com/cloud/vm/BareMetalVmService.java new file mode 100644 index 00000000000..eed95666d99 --- /dev/null +++ b/api/src/com/cloud/vm/BareMetalVmService.java @@ -0,0 +1,4 @@ +package com.cloud.vm; + +public interface BareMetalVmService extends UserVmService { +} diff --git a/server/src/com/cloud/configuration/DefaultComponentLibrary.java b/server/src/com/cloud/configuration/DefaultComponentLibrary.java index a94fa8182f9..2d8ea99e074 100644 --- a/server/src/com/cloud/configuration/DefaultComponentLibrary.java +++ b/server/src/com/cloud/configuration/DefaultComponentLibrary.java @@ -147,6 +147,8 @@ import com.cloud.vm.dao.SecondaryStorageVmDaoImpl; import com.cloud.vm.dao.UserVmDaoImpl; import com.cloud.vm.dao.UserVmDetailsDaoImpl; import com.cloud.vm.dao.VMInstanceDaoImpl; +import com.cloud.vm.BareMetalVmManagerImpl;; + public class DefaultComponentLibrary implements ComponentLibrary { @@ -323,6 +325,7 @@ public class DefaultComponentLibrary implements ComponentLibrary { addManager("ClusteredAgentManager", ClusteredAgentManagerImpl.class); addManager("VirtualMachineManager", ClusteredVirtualMachineManagerImpl.class); addManager("HypervisorGuruManager", HypervisorGuruManagerImpl.class); + addManager("BareMetalVmManager", BareMetalVmManagerImpl.class); ComponentInfo info = addManager("ConsoleProxyManager", ConsoleProxyManagerImpl.class); info.addParameter("consoleproxy.sslEnabled", "true"); diff --git a/server/src/com/cloud/template/TemplateManagerImpl.java b/server/src/com/cloud/template/TemplateManagerImpl.java index 8c2db9bc5e7..927d4bb2e6f 100755 --- a/server/src/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/com/cloud/template/TemplateManagerImpl.java @@ -349,7 +349,8 @@ public class TemplateManagerImpl implements TemplateManager, Manager, TemplateSe &&(!url.toLowerCase().endsWith("qcow2"))&&(!url.toLowerCase().endsWith("qcow2.zip")) &&(!url.toLowerCase().endsWith("qcow2.bz2"))&&(!url.toLowerCase().endsWith("qcow2.gz")) &&(!url.toLowerCase().endsWith("ova"))&&(!url.toLowerCase().endsWith("ova.zip")) - &&(!url.toLowerCase().endsWith("ova.bz2"))&&(!url.toLowerCase().endsWith("ova.gz"))){ + &&(!url.toLowerCase().endsWith("ova.bz2"))&&(!url.toLowerCase().endsWith("ova.gz")) + &&hypervisorType != HypervisorType.BareMetal){ throw new InvalidParameterValueException("Please specify a valid "+format.toLowerCase()); } @@ -381,25 +382,28 @@ public class TemplateManagerImpl implements TemplateManager, Manager, TemplateSe } URI uri = new URI(url); - if ((uri.getScheme() == null) || (!uri.getScheme().equalsIgnoreCase("http") && !uri.getScheme().equalsIgnoreCase("https") && !uri.getScheme().equalsIgnoreCase("file"))) { + if ((uri.getScheme() == null) || (!uri.getScheme().equalsIgnoreCase("http") && !uri.getScheme().equalsIgnoreCase("https") && !uri.getScheme().equalsIgnoreCase("file") && !uri.getScheme().equalsIgnoreCase("baremetal"))) { throw new IllegalArgumentException("Unsupported scheme for url: " + url); } - int port = uri.getPort(); - if (!(port == 80 || port == 443 || port == -1)) { - throw new IllegalArgumentException("Only ports 80 and 443 are allowed"); - } - String host = uri.getHost(); - try { - InetAddress hostAddr = InetAddress.getByName(host); - if (hostAddr.isAnyLocalAddress() || hostAddr.isLinkLocalAddress() || hostAddr.isLoopbackAddress() || hostAddr.isMulticastAddress() ) { - throw new IllegalArgumentException("Illegal host specified in url"); - } - if (hostAddr instanceof Inet6Address) { - throw new IllegalArgumentException("IPV6 addresses not supported (" + hostAddr.getHostAddress() + ")"); - } - } catch (UnknownHostException uhe) { - throw new IllegalArgumentException("Unable to resolve " + host); - } + + if (!uri.getScheme().equalsIgnoreCase("baremetal")) { + int port = uri.getPort(); + if (!(port == 80 || port == 443 || port == -1)) { + throw new IllegalArgumentException("Only ports 80 and 443 are allowed"); + } + String host = uri.getHost(); + try { + InetAddress hostAddr = InetAddress.getByName(host); + if (hostAddr.isAnyLocalAddress() || hostAddr.isLinkLocalAddress() || hostAddr.isLoopbackAddress() || hostAddr.isMulticastAddress()) { + throw new IllegalArgumentException("Illegal host specified in url"); + } + if (hostAddr instanceof Inet6Address) { + throw new IllegalArgumentException("IPV6 addresses not supported (" + hostAddr.getHostAddress() + ")"); + } + } catch (UnknownHostException uhe) { + throw new IllegalArgumentException("Unable to resolve " + host); + } + } // Check that the resource limit for templates/ISOs won't be exceeded UserVO user = _userDao.findById(userId); diff --git a/server/src/com/cloud/vm/BareMetalVmManager.java b/server/src/com/cloud/vm/BareMetalVmManager.java new file mode 100644 index 00000000000..9e952a4dccb --- /dev/null +++ b/server/src/com/cloud/vm/BareMetalVmManager.java @@ -0,0 +1,5 @@ +package com.cloud.vm; + +public interface BareMetalVmManager extends UserVmManager { + +} diff --git a/server/src/com/cloud/vm/BareMetalVmManagerImpl.java b/server/src/com/cloud/vm/BareMetalVmManagerImpl.java new file mode 100644 index 00000000000..3238e573947 --- /dev/null +++ b/server/src/com/cloud/vm/BareMetalVmManagerImpl.java @@ -0,0 +1,280 @@ +package com.cloud.vm; + +import java.util.ArrayList; +import java.util.List; + +import javax.ejb.Local; + +import org.apache.log4j.Logger; + +import com.cloud.api.commands.AttachVolumeCmd; +import com.cloud.api.commands.CreateTemplateCmd; +import com.cloud.api.commands.DeployVMCmd; +import com.cloud.api.commands.DetachVolumeCmd; +import com.cloud.api.commands.UpgradeVMCmd; +import com.cloud.configuration.ResourceCount.ResourceType; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.domain.DomainVO; +import com.cloud.event.EventTypes; +import com.cloud.event.UsageEventVO; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.exception.StorageUnavailableException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Network; +import com.cloud.network.NetworkVO; +import com.cloud.network.Networks.TrafficType; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.storage.Storage; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.Volume; +import com.cloud.storage.Storage.TemplateType; +import com.cloud.user.Account; +import com.cloud.user.SSHKeyPair; +import com.cloud.user.UserContext; +import com.cloud.uservm.UserVm; +import com.cloud.utils.Pair; +import com.cloud.utils.component.Manager; +import com.cloud.utils.db.DB; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; + +@Local(value={BareMetalVmManager.class, BareMetalVmService.class}) +public class BareMetalVmManagerImpl extends UserVmManagerImpl implements BareMetalVmManager, BareMetalVmService, Manager { + private static final Logger s_logger = Logger.getLogger(BareMetalVmManagerImpl.class); + + @Override + public boolean attachISOToVM(long vmId, long isoId, boolean attach) { + s_logger.warn("attachISOToVM is not supported by Bare Metal, just fake a true"); + return true; + } + + @Override + public Volume attachVolumeToVM(AttachVolumeCmd command) { + s_logger.warn("attachVolumeToVM is not supported by Bare Metal, return null"); + return null; + } + + @Override + public Volume detachVolumeFromVM(DetachVolumeCmd cmd) { + s_logger.warn("detachVolumeFromVM is not supported by Bare Metal, return null"); + return null; + } + + @Override + public UserVm upgradeVirtualMachine(UpgradeVMCmd cmd) { + s_logger.warn("upgradeVirtualMachine is not supported by Bare Metal, return null"); + return null; + } + + @Override + public VMTemplateVO createPrivateTemplateRecord(CreateTemplateCmd cmd) throws InvalidParameterValueException, PermissionDeniedException, ResourceAllocationException { + s_logger.warn("createPrivateTemplateRecord is not supported by Bare Metal, return null"); + return null; + } + + @Override @DB + public VMTemplateVO createPrivateTemplate(CreateTemplateCmd command) throws CloudRuntimeException { + s_logger.warn("createPrivateTemplate is not supported by Bare Metal, return null"); + return null; + } + + @Override + public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException, + StorageUnavailableException, ResourceAllocationException { + Account caller = UserContext.current().getCaller(); + + String accountName = cmd.getAccountName(); + Long domainId = cmd.getDomainId(); + List networkList = cmd.getNetworkIds(); + String group = cmd.getGroup(); + + Account owner = _accountDao.findActiveAccount(accountName, domainId); + if (owner == null) { + throw new InvalidParameterValueException("Unable to find account " + accountName + " in domain " + domainId); + } + + _accountMgr.checkAccess(caller, owner); + long accountId = owner.getId(); + + DataCenterVO dc = _dcDao.findById(cmd.getZoneId()); + if (dc == null) { + throw new InvalidParameterValueException("Unable to find zone: " + cmd.getZoneId()); + } + + if (dc.getDomainId() != null) { + DomainVO domain = _domainDao.findById(dc.getDomainId()); + if (domain == null) { + throw new CloudRuntimeException("Unable to find the domain " + dc.getDomainId() + " for the zone: " + dc); + } + _accountMgr.checkAccess(caller, domain); + _accountMgr.checkAccess(owner, domain); + } + + // check if account/domain is with in resource limits to create a new vm + if (_accountMgr.resourceLimitExceeded(owner, ResourceType.user_vm)) { + ResourceAllocationException rae = new ResourceAllocationException("Maximum number of virtual machines for account: " + owner.getAccountName() + + " has been exceeded."); + rae.setResourceType("vm"); + throw rae; + } + + ServiceOfferingVO offering = _serviceOfferingDao.findById(cmd.getServiceOfferingId()); + if (offering == null || offering.getRemoved() != null) { + throw new InvalidParameterValueException("Unable to find service offering: " + cmd.getServiceOfferingId()); + } + + VMTemplateVO template = _templateDao.findById(cmd.getTemplateId()); + // Make sure a valid template ID was specified + if (template == null || template.getRemoved() != null) { + throw new InvalidParameterValueException("Unable to use template " + cmd.getTemplateId()); + } + + if (template.getTemplateType().equals(TemplateType.SYSTEM)) { + throw new InvalidParameterValueException("Unable to use system template " + cmd.getTemplateId()+" to deploy a user vm"); + } + + if (template.getFormat() != Storage.ImageFormat.BAREMETAL) { + throw new InvalidParameterValueException("Unable to use non Bare Metal template" + cmd.getTemplateId() +" to deploy a bare metal vm"); + } + + String userData = cmd.getUserData(); + byte [] decodedUserData = null; + if (userData != null) { + if (userData.length() >= 2 * MAX_USER_DATA_LENGTH_BYTES) { + throw new InvalidParameterValueException("User data is too long"); + } + decodedUserData = org.apache.commons.codec.binary.Base64.decodeBase64(userData.getBytes()); + if (decodedUserData.length > MAX_USER_DATA_LENGTH_BYTES){ + throw new InvalidParameterValueException("User data is too long"); + } + if (decodedUserData.length < 1) { + throw new InvalidParameterValueException("User data is too short"); + } + } + + // 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()); + + s_logger.debug("Allocating in the DB for bare metal vm"); + + if (dc.getNetworkType() != NetworkType.Basic || networkList != null) { + s_logger.warn("Bare Metal only supports basical network mode now, switch to baisc network automatically"); + } + + Network defaultNetwork = _networkMgr.getSystemNetworkByZoneAndTrafficType(dc.getId(), TrafficType.Guest); + if (defaultNetwork == null) { + throw new InvalidParameterValueException("Unable to find a default network to start a vm"); + } + + networkList = new ArrayList(); + networkList.add(defaultNetwork.getId()); + + List> networks = new ArrayList>(); + short defaultNetworkNumber = 0; + for (Long networkId : networkList) { + NetworkVO network = _networkDao.findById(networkId); + if (network == null) { + throw new InvalidParameterValueException("Unable to find network by id " + networkId); + } else { + if (!network.isShared()) { + //Check account permissions + List networkMap = _networkDao.listBy(accountId, networkId); + if (networkMap == null || networkMap.isEmpty()) { + throw new PermissionDeniedException("Unable to create a vm using network with id " + networkId + ", permission denied"); + } + } + + if (network.isDefault()) { + defaultNetworkNumber++; + } + networks.add(new Pair(network, null)); + } + } + + //at least one network default network has to be set + if (defaultNetworkNumber == 0) { + throw new InvalidParameterValueException("At least 1 default network has to be specified for the vm"); + } else if (defaultNetworkNumber >1) { + throw new InvalidParameterValueException("Only 1 default network per vm is supported"); + } + + long id = _vmDao.getNextInSequence(Long.class, "id"); + + String hostName = cmd.getName(); + String instanceName = VirtualMachineName.getVmName(id, owner.getId(), _instance); + if (hostName == null) { + hostName = instanceName; + } else { + //verify hostName (hostname doesn't have to be unique) + if (!NetUtils.verifyDomainNameLabel(hostName, true)) { + throw new InvalidParameterValueException("Invalid name. Vm name can contain ASCII letters 'a' through 'z', the digits '0' through '9', " + + "and the hyphen ('-'), must be between 1 and 63 characters long, and can't start or end with \"-\" and can't start with digit"); + } + } + + UserVmVO vm = new UserVmVO(id, instanceName, cmd.getDisplayName(), template.getId(), HypervisorType.BareMetal, + 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, null, null, 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); + } + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Successfully allocated DB entry for " + vm); + } + UserContext.current().setEventDetails("Vm Id: " + vm.getId()); + UsageEventVO usageEvent = new UsageEventVO(EventTypes.EVENT_VM_CREATE, accountId, dc.getId(), vm.getId(), vm.getName(), offering.getId(), + template.getId(), null); + _usageEventDao.persist(usageEvent); + + _accountMgr.incrementResourceCount(accountId, ResourceType.user_vm); + + // Assign instance to the group + try { + if (group != null) { + boolean addToGroup = addInstanceToGroup(Long.valueOf(id), group); + if (!addToGroup) { + throw new CloudRuntimeException("Unable to assign Vm to the group " + group); + } + } + } catch (Exception ex) { + throw new CloudRuntimeException("Unable to assign Vm to the group " + group); + } + + return vm; + } + + public UserVm startVirtualMachine(DeployVMCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ConcurrentOperationException { + return null; + } +} diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index e221e58a0bf..0104af99c2c 100755 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -1848,7 +1848,7 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager } } - private boolean validPassword(String password) { + protected boolean validPassword(String password) { if (password == null || password.length() == 0) { return false; } diff --git a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java index 06f3274d8d7..f5de5cccd1f 100755 --- a/server/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/server/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -246,9 +246,11 @@ public class VirtualMachineManagerImpl implements VirtualMachineManager, Listene if (template.getFormat() == ImageFormat.ISO) { _storageMgr.allocateRawVolume(VolumeType.ROOT, "ROOT-" + vm.getId(), rootDiskOffering.first(), rootDiskOffering.second(), vm, owner); - } else { + } else if (template.getFormat() == ImageFormat.BAREMETAL) { + }else { _storageMgr.allocateTemplatedVolume(VolumeType.ROOT, "ROOT-" + vm.getId(), rootDiskOffering.first(), template, vm, owner); } + for (Pair offering : dataDiskOfferings) { _storageMgr.allocateRawVolume(VolumeType.DATADISK, "DATA-" + vm.getId(), offering.first(), offering.second(), vm, owner); }