| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| package com.cloud.network.lb; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Random; |
| |
| import javax.inject.Inject; |
| |
| import org.apache.cloudstack.api.command.user.loadbalancer.CreateLoadBalancerRuleCmd; |
| import org.apache.cloudstack.context.CallContext; |
| import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; |
| import org.apache.log4j.Logger; |
| |
| import com.cloud.configuration.ConfigurationManagerImpl; |
| import com.cloud.dc.DataCenter; |
| import com.cloud.dc.Pod; |
| import com.cloud.dc.PodVlanMapVO; |
| import com.cloud.dc.Vlan.VlanType; |
| import com.cloud.dc.dao.ClusterDao; |
| import com.cloud.dc.dao.DataCenterDao; |
| import com.cloud.dc.dao.HostPodDao; |
| import com.cloud.dc.dao.PodVlanMapDao; |
| import com.cloud.deploy.DataCenterDeployment; |
| import com.cloud.deploy.DeployDestination; |
| import com.cloud.exception.ConcurrentOperationException; |
| import com.cloud.exception.InsufficientAddressCapacityException; |
| import com.cloud.exception.InsufficientCapacityException; |
| import com.cloud.exception.InvalidParameterValueException; |
| import com.cloud.exception.NetworkRuleConflictException; |
| import com.cloud.network.ElasticLbVmMapVO; |
| import com.cloud.network.IpAddressManager; |
| import com.cloud.network.Network; |
| import com.cloud.network.Network.Provider; |
| import com.cloud.network.Network.Service; |
| import com.cloud.network.NetworkModel; |
| import com.cloud.network.PhysicalNetworkServiceProvider; |
| import com.cloud.network.VirtualRouterProvider; |
| import com.cloud.network.VirtualRouterProvider.Type; |
| import com.cloud.network.addr.PublicIp; |
| import com.cloud.network.dao.IPAddressDao; |
| import com.cloud.network.dao.IPAddressVO; |
| import com.cloud.network.dao.LoadBalancerDao; |
| import com.cloud.network.dao.LoadBalancerVO; |
| import com.cloud.network.dao.NetworkDao; |
| import com.cloud.network.dao.NetworkVO; |
| import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; |
| import com.cloud.network.dao.VirtualRouterProviderDao; |
| import com.cloud.network.lb.dao.ElasticLbVmMapDao; |
| import com.cloud.network.router.VirtualRouter.RedundantState; |
| import com.cloud.network.router.VirtualRouter.Role; |
| import com.cloud.network.rules.LoadBalancer; |
| import com.cloud.offering.NetworkOffering; |
| import com.cloud.offering.ServiceOffering; |
| import com.cloud.offerings.dao.NetworkOfferingDao; |
| import com.cloud.service.ServiceOfferingVO; |
| import com.cloud.service.dao.ServiceOfferingDao; |
| import com.cloud.storage.VMTemplateVO; |
| import com.cloud.storage.dao.VMTemplateDao; |
| import com.cloud.user.Account; |
| import com.cloud.user.AccountService; |
| import com.cloud.user.UserVO; |
| import com.cloud.user.dao.AccountDao; |
| import com.cloud.user.dao.UserDao; |
| import com.cloud.utils.db.DB; |
| import com.cloud.utils.db.SearchBuilder; |
| import com.cloud.utils.db.SearchCriteria; |
| import com.cloud.utils.db.Transaction; |
| import com.cloud.utils.db.TransactionCallbackWithException; |
| import com.cloud.utils.db.TransactionStatus; |
| import com.cloud.utils.exception.CloudRuntimeException; |
| import com.cloud.vm.DomainRouterVO; |
| import com.cloud.vm.NicProfile; |
| import com.cloud.vm.VirtualMachine.State; |
| import com.cloud.vm.VirtualMachineManager; |
| import com.cloud.vm.VirtualMachineName; |
| import com.cloud.vm.VirtualMachineProfile; |
| import com.cloud.vm.VirtualMachineProfile.Param; |
| import com.cloud.vm.dao.DomainRouterDao; |
| |
| public class LoadBalanceRuleHandler { |
| |
| private static final Logger s_logger = Logger.getLogger(LoadBalanceRuleHandler.class); |
| |
| @Inject |
| private IPAddressDao _ipAddressDao; |
| @Inject |
| private NetworkModel _networkModel; |
| @Inject |
| private NetworkOrchestrationService _networkMgr; |
| @Inject |
| private final LoadBalancerDao _loadBalancerDao = null; |
| @Inject |
| private LoadBalancingRulesManager _lbMgr; |
| @Inject |
| private final DomainRouterDao _routerDao = null; |
| @Inject |
| protected HostPodDao _podDao = null; |
| @Inject |
| protected ClusterDao _clusterDao; |
| @Inject |
| private final DataCenterDao _dcDao = null; |
| @Inject |
| private IpAddressManager _ipAddrMgr; |
| @Inject |
| protected NetworkDao _networkDao; |
| @Inject |
| protected NetworkOfferingDao _networkOfferingDao; |
| @Inject |
| private final VMTemplateDao _templateDao = null; |
| @Inject |
| private VirtualMachineManager _itMgr; |
| @Inject |
| private AccountService _accountService; |
| @Inject |
| private LoadBalancerDao _lbDao; |
| @Inject |
| private PodVlanMapDao _podVlanMapDao; |
| @Inject |
| private ElasticLbVmMapDao _elbVmMapDao; |
| @Inject |
| private AccountDao _accountDao; |
| @Inject |
| private PhysicalNetworkServiceProviderDao _physicalProviderDao; |
| @Inject |
| private VirtualRouterProviderDao _vrProviderDao; |
| @Inject |
| private ServiceOfferingDao _serviceOfferingDao; |
| @Inject |
| private UserDao _userDao; |
| |
| static final private String ELB_VM_NAME_PREFIX = "l"; |
| |
| private final String _instance; |
| private final Account _systemAcct; |
| |
| public LoadBalanceRuleHandler(String instance, Account systemAcct) { |
| _instance = instance; |
| _systemAcct = systemAcct; |
| } |
| |
| public void handleDeleteLoadBalancerRule(final LoadBalancer lb, final long userId, final Account caller) { |
| final List<LoadBalancerVO> remainingLbs = _loadBalancerDao.listByIpAddress(lb.getSourceIpAddressId()); |
| if (remainingLbs.size() == 0) { |
| s_logger.debug("ELB mgr: releasing ip " + lb.getSourceIpAddressId() + " since no LB rules remain for this ip address"); |
| releaseIp(lb.getSourceIpAddressId(), userId, caller); |
| } |
| } |
| |
| public LoadBalancer handleCreateLoadBalancerRule(final CreateLoadBalancerRuleCmd lb, Account account, final long networkId) throws InsufficientAddressCapacityException, |
| NetworkRuleConflictException { |
| //this part of code is executed when the LB provider is Elastic Load Balancer vm |
| if (!_networkModel.isProviderSupportServiceInNetwork(lb.getNetworkId(), Service.Lb, Provider.ElasticLoadBalancerVm)) { |
| return null; |
| } |
| |
| final Long ipId = lb.getSourceIpAddressId(); |
| if (ipId != null) { |
| return null; |
| } |
| |
| account = _accountDao.acquireInLockTable(account.getId()); |
| if (account == null) { |
| s_logger.warn("ELB: CreateLoadBalancer: Failed to acquire lock on account"); |
| throw new CloudRuntimeException("Failed to acquire lock on account"); |
| } |
| try { |
| return handleCreateLoadBalancerRuleWithLock(lb, account, networkId); |
| } finally { |
| if (account != null) { |
| _accountDao.releaseFromLockTable(account.getId()); |
| } |
| } |
| } |
| |
| private DomainRouterVO deployLoadBalancerVM(final Long networkId, final IPAddressVO ipAddr) { |
| final NetworkVO network = _networkDao.findById(networkId); |
| final DataCenter dc = _dcDao.findById(network.getDataCenterId()); |
| final Long podId = getPodIdForDirectIp(ipAddr); |
| final Pod pod = podId == null ? null : _podDao.findById(podId); |
| final Map<VirtualMachineProfile.Param, Object> params = new HashMap<VirtualMachineProfile.Param, Object>(1); |
| params.put(VirtualMachineProfile.Param.ReProgramGuestNetworks, true); |
| final Account owner = _accountService.getActiveAccountByName("system", new Long(1)); |
| final DeployDestination dest = new DeployDestination(dc, pod, null, null); |
| s_logger.debug("About to deploy ELB vm "); |
| |
| try { |
| final DomainRouterVO elbVm = deployELBVm(network, dest, owner, params); |
| if (elbVm == null) { |
| throw new InvalidParameterValueException("Could not deploy or find existing ELB VM"); |
| } |
| s_logger.debug("Deployed ELB vm = " + elbVm); |
| |
| return elbVm; |
| |
| } catch (final Throwable t) { |
| s_logger.warn("Error while deploying ELB VM: ", t); |
| return null; |
| } |
| |
| } |
| |
| private DomainRouterVO deployELBVm(Network guestNetwork, final DeployDestination dest, Account owner, final Map<Param, Object> params) throws ConcurrentOperationException, |
| InsufficientCapacityException { |
| final long dcId = dest.getDataCenter().getId(); |
| |
| // lock guest network |
| final Long guestNetworkId = guestNetwork.getId(); |
| guestNetwork = _networkDao.acquireInLockTable(guestNetworkId); |
| |
| if (guestNetwork == null) { |
| throw new ConcurrentOperationException("Unable to acquire network lock: " + guestNetworkId); |
| } |
| |
| try { |
| |
| if (_networkModel.isNetworkSystem(guestNetwork) || guestNetwork.getGuestType() == Network.GuestType.Shared) { |
| owner = _accountService.getSystemAccount(); |
| } |
| |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Starting a ELB vm for network configurations: " + guestNetwork + " in " + dest); |
| } |
| assert guestNetwork.getState() == Network.State.Implemented || guestNetwork.getState() == Network.State.Setup || guestNetwork.getState() == Network.State.Implementing : "Network is not yet fully implemented: " |
| + guestNetwork; |
| |
| DataCenterDeployment plan = null; |
| DomainRouterVO elbVm = null; |
| |
| plan = new DataCenterDeployment(dcId, dest.getPod().getId(), null, null, null, null); |
| |
| if (elbVm == null) { |
| final long id = _routerDao.getNextInSequence(Long.class, "id"); |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Creating the ELB vm " + id); |
| } |
| |
| final List<? extends NetworkOffering> offerings = _networkModel.getSystemAccountNetworkOfferings(NetworkOffering.SystemControlNetwork); |
| final NetworkOffering controlOffering = offerings.get(0); |
| final Network controlConfig = _networkMgr.setupNetwork(_systemAcct, controlOffering, plan, null, null, false).get(0); |
| |
| final LinkedHashMap<Network, List<? extends NicProfile>> networks = new LinkedHashMap<Network, List<? extends NicProfile>>(2); |
| final NicProfile guestNic = new NicProfile(); |
| guestNic.setDefaultNic(true); |
| networks.put(controlConfig, new ArrayList<NicProfile>()); |
| networks.put(guestNetwork, new ArrayList<NicProfile>(Arrays.asList(guestNic))); |
| |
| final VMTemplateVO template = _templateDao.findSystemVMTemplate(dcId); |
| |
| final String typeString = "ElasticLoadBalancerVm"; |
| final Long physicalNetworkId = _networkModel.getPhysicalNetworkId(guestNetwork); |
| final PhysicalNetworkServiceProvider provider = _physicalProviderDao.findByServiceProvider(physicalNetworkId, typeString); |
| if (provider == null) { |
| throw new CloudRuntimeException("Cannot find service provider " + typeString + " in physical network " + physicalNetworkId); |
| } |
| final VirtualRouterProvider vrProvider = _vrProviderDao.findByNspIdAndType(provider.getId(), Type.ElasticLoadBalancerVm); |
| if (vrProvider == null) { |
| throw new CloudRuntimeException("Cannot find virtual router provider " + typeString + " as service provider " + provider.getId()); |
| } |
| |
| long userId = CallContext.current().getCallingUserId(); |
| if (CallContext.current().getCallingAccount().getId() != owner.getId()) { |
| List<UserVO> userVOs = _userDao.listByAccount(owner.getAccountId()); |
| if (!userVOs.isEmpty()) { |
| userId = userVOs.get(0).getId(); |
| } |
| } |
| |
| ServiceOfferingVO elasticLbVmOffering = _serviceOfferingDao.findDefaultSystemOffering(ServiceOffering.elbVmDefaultOffUniqueName, ConfigurationManagerImpl.SystemVMUseLocalStorage.valueIn(dest.getDataCenter().getId())); |
| elbVm = new DomainRouterVO(id, elasticLbVmOffering.getId(), vrProvider.getId(), VirtualMachineName.getSystemVmName(id, _instance, ELB_VM_NAME_PREFIX), |
| template.getId(), template.getHypervisorType(), template.getGuestOSId(), owner.getDomainId(), owner.getId(), userId, false, RedundantState.UNKNOWN, |
| elasticLbVmOffering.isOfferHA(), false, null); |
| elbVm.setRole(Role.LB); |
| elbVm = _routerDao.persist(elbVm); |
| _itMgr.allocate(elbVm.getInstanceName(), template, elasticLbVmOffering, networks, plan, null); |
| elbVm = _routerDao.findById(elbVm.getId()); |
| //TODO: create usage stats |
| } |
| |
| final State state = elbVm.getState(); |
| if (state != State.Running) { |
| elbVm = start(elbVm, params); |
| } |
| |
| return elbVm; |
| } finally { |
| _networkDao.releaseFromLockTable(guestNetworkId); |
| } |
| } |
| |
| private void releaseIp(final long ipId, final long userId, final Account caller) { |
| s_logger.info("ELB: Release public IP for loadbalancing " + ipId); |
| final IPAddressVO ipvo = _ipAddressDao.findById(ipId); |
| ipvo.setAssociatedWithNetworkId(null); |
| _ipAddressDao.update(ipvo.getId(), ipvo); |
| _ipAddrMgr.disassociatePublicIpAddress(ipId, userId, caller); |
| _ipAddressDao.unassignIpAddress(ipId); |
| } |
| |
| protected Long getPodIdForDirectIp(final IPAddressVO ipAddr) { |
| final PodVlanMapVO podVlanMaps = _podVlanMapDao.listPodVlanMapsByVlan(ipAddr.getVlanId()); |
| if (podVlanMaps == null) { |
| return null; |
| } else { |
| return podVlanMaps.getPodId(); |
| } |
| } |
| |
| private LoadBalancer handleCreateLoadBalancerRuleWithLock(final CreateLoadBalancerRuleCmd lb, final Account account, final long networkId) throws InsufficientAddressCapacityException, |
| NetworkRuleConflictException { |
| Long ipId = null; |
| boolean newIp = false; |
| List<LoadBalancerVO> existingLbs = findExistingLoadBalancers(lb.getName(), lb.getSourceIpAddressId(), lb.getAccountId(), lb.getDomainId(), lb.getSourcePortStart()); |
| if (existingLbs == null) { |
| existingLbs = findExistingLoadBalancers(lb.getName(), lb.getSourceIpAddressId(), lb.getAccountId(), lb.getDomainId(), null); |
| if (existingLbs == null) { |
| if (lb.getSourceIpAddressId() != null) { |
| throwExceptionIfSuppliedlLbNameIsNotAssociatedWithIpAddress(lb); |
| } else { |
| s_logger.debug("Could not find any existing frontend ips for this account for this LB rule, acquiring a new frontent IP for ELB"); |
| final PublicIp ip = allocDirectIp(account, networkId); |
| ipId = ip.getId(); |
| newIp = true; |
| } |
| } else { |
| ipId = existingLbs.get(0).getSourceIpAddressId(); |
| s_logger.debug("ELB: Found existing frontend ip for this account for this LB rule " + ipId); |
| } |
| } else { |
| s_logger.warn("ELB: Found existing load balancers matching requested new LB"); |
| throw new NetworkRuleConflictException("ELB: Found existing load balancers matching requested new LB"); |
| } |
| |
| final IPAddressVO ipAddr = _ipAddressDao.findById(ipId); |
| |
| LoadBalancer result = null; |
| try { |
| lb.setSourceIpAddressId(ipId); |
| |
| result = _lbMgr.createPublicLoadBalancer(lb.getXid(), lb.getName(), lb.getDescription(), lb.getSourcePortStart(), lb.getDefaultPortStart(), ipId.longValue(), |
| lb.getProtocol(), lb.getAlgorithm(), false, CallContext.current(), lb.getLbProtocol(), true, null); |
| } catch (final NetworkRuleConflictException e) { |
| s_logger.warn("Failed to create LB rule, not continuing with ELB deployment"); |
| if (newIp) { |
| releaseIp(ipId, CallContext.current().getCallingUserId(), account); |
| } |
| throw e; |
| } |
| |
| DomainRouterVO elbVm = null; |
| |
| if (existingLbs == null) { |
| elbVm = findElbVmWithCapacity(ipAddr); |
| if (elbVm == null) { |
| elbVm = deployLoadBalancerVM(networkId, ipAddr); |
| if (elbVm == null) { |
| final Network network = _networkModel.getNetwork(networkId); |
| s_logger.warn("Failed to deploy a new ELB vm for ip " + ipAddr + " in network " + network + "lb name=" + lb.getName()); |
| if (newIp) { |
| releaseIp(ipId, CallContext.current().getCallingUserId(), account); |
| } |
| } |
| } |
| |
| } else { |
| final ElasticLbVmMapVO elbVmMap = _elbVmMapDao.findOneByIp(ipId); |
| if (elbVmMap != null) { |
| elbVm = _routerDao.findById(elbVmMap.getElbVmId()); |
| } |
| } |
| |
| if (elbVm == null) { |
| s_logger.warn("No ELB VM can be found or deployed"); |
| s_logger.warn("Deleting LB since we failed to deploy ELB VM"); |
| _lbDao.remove(result.getId()); |
| return null; |
| } |
| |
| final ElasticLbVmMapVO mapping = new ElasticLbVmMapVO(ipId, elbVm.getId(), result.getId()); |
| _elbVmMapDao.persist(mapping); |
| return result; |
| } |
| |
| private void throwExceptionIfSuppliedlLbNameIsNotAssociatedWithIpAddress(final CreateLoadBalancerRuleCmd lb) { |
| final List<LoadBalancerVO> existingLbs = findExistingLoadBalancers(lb.getName(), null, lb.getAccountId(), lb.getDomainId(), null); |
| if (existingLbs != null) { |
| throw new InvalidParameterValueException("Supplied LB name " + lb.getName() + " is not associated with IP " + lb.getSourceIpAddressId()); |
| } |
| } |
| |
| protected List<LoadBalancerVO> findExistingLoadBalancers(final String lbName, final Long ipId, final Long accountId, final Long domainId, final Integer publicPort) { |
| final SearchBuilder<LoadBalancerVO> sb = _lbDao.createSearchBuilder(); |
| sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ); |
| sb.and("accountId", sb.entity().getAccountId(), SearchCriteria.Op.EQ); |
| sb.and("publicPort", sb.entity().getSourcePortStart(), SearchCriteria.Op.EQ); |
| if (ipId != null) { |
| sb.and("sourceIpAddress", sb.entity().getSourceIpAddressId(), SearchCriteria.Op.EQ); |
| } |
| if (domainId != null) { |
| sb.and("domainId", sb.entity().getDomainId(), SearchCriteria.Op.EQ); |
| } |
| if (publicPort != null) { |
| sb.and("publicPort", sb.entity().getSourcePortStart(), SearchCriteria.Op.EQ); |
| } |
| final SearchCriteria<LoadBalancerVO> sc = sb.create(); |
| sc.setParameters("name", lbName); |
| sc.setParameters("accountId", accountId); |
| if (ipId != null) { |
| sc.setParameters("sourceIpAddress", ipId); |
| } |
| if (domainId != null) { |
| sc.setParameters("domainId", domainId); |
| } |
| if (publicPort != null) { |
| sc.setParameters("publicPort", publicPort); |
| } |
| final List<LoadBalancerVO> lbs = _lbDao.search(sc, null); |
| |
| return lbs == null || lbs.size() == 0 ? null : lbs; |
| } |
| |
| @DB |
| private PublicIp allocDirectIp(final Account account, final long guestNetworkId) throws InsufficientAddressCapacityException { |
| return Transaction.execute(new TransactionCallbackWithException<PublicIp, InsufficientAddressCapacityException>() { |
| @Override |
| public PublicIp doInTransaction(final TransactionStatus status) throws InsufficientAddressCapacityException { |
| final Network frontEndNetwork = _networkModel.getNetwork(guestNetworkId); |
| |
| final PublicIp ip = _ipAddrMgr.assignPublicIpAddress(frontEndNetwork.getDataCenterId(), null, account, VlanType.DirectAttached, frontEndNetwork.getId(), null, true, false); |
| final IPAddressVO ipvo = _ipAddressDao.findById(ip.getId()); |
| ipvo.setAssociatedWithNetworkId(frontEndNetwork.getId()); |
| _ipAddressDao.update(ipvo.getId(), ipvo); |
| s_logger.info("Acquired frontend IP for ELB " + ip); |
| |
| return ip; |
| } |
| }); |
| } |
| |
| protected DomainRouterVO findElbVmWithCapacity(final IPAddressVO ipAddr) { |
| final List<DomainRouterVO> unusedElbVms = _elbVmMapDao.listUnusedElbVms(); |
| if (unusedElbVms.size() > 0) { |
| final List<DomainRouterVO> candidateVms = new ArrayList<DomainRouterVO>(); |
| for (final DomainRouterVO candidateVm : unusedElbVms) { |
| addCandidateVmIsPodIpMatches(candidateVm, getPodIdForDirectIp(ipAddr), candidateVms); |
| } |
| return candidateVms.size() == 0 ? null : candidateVms.get(new Random().nextInt(candidateVms.size())); |
| } |
| return null; |
| } |
| |
| protected static void addCandidateVmIsPodIpMatches(final DomainRouterVO candidateVm, final Long podIdForDirectIp, final List<DomainRouterVO> candidateVms) { |
| if (candidateVm.getPodIdToDeployIn().equals(podIdForDirectIp)) { |
| candidateVms.add(candidateVm); |
| } |
| } |
| |
| protected DomainRouterVO start(final DomainRouterVO elbVm, final Map<Param, Object> params) throws ConcurrentOperationException { |
| s_logger.debug("Starting ELB VM " + elbVm); |
| _itMgr.start(elbVm.getUuid(), params); |
| return _routerDao.findById(elbVm.getId()); |
| } |
| } |