blob: 0a9b4a7131a3a2ffdc20902bf813bee7c204ce39 [file] [log] [blame]
// 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 org.apache.cloudstack.network.element;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.apache.cloudstack.api.command.admin.internallb.ConfigureInternalLoadBalancerElementCmd;
import org.apache.cloudstack.api.command.admin.internallb.CreateInternalLoadBalancerElementCmd;
import org.apache.cloudstack.api.command.admin.internallb.ListInternalLoadBalancerElementsCmd;
import org.apache.cloudstack.lb.dao.ApplicationLoadBalancerRuleDao;
import org.apache.cloudstack.network.lb.InternalLoadBalancerVMManager;
import com.cloud.agent.api.to.LoadBalancerTO;
import com.cloud.configuration.ConfigurationManager;
import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenter.NetworkType;
import com.cloud.deploy.DeployDestination;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.network.Network;
import com.cloud.network.Network.Capability;
import com.cloud.network.Network.Provider;
import com.cloud.network.Network.Service;
import com.cloud.network.NetworkModel;
import com.cloud.network.Networks.TrafficType;
import com.cloud.network.PhysicalNetworkServiceProvider;
import com.cloud.network.PublicIpAddress;
import com.cloud.network.VirtualRouterProvider;
import com.cloud.network.VirtualRouterProvider.Type;
import com.cloud.network.dao.NetworkServiceMapDao;
import com.cloud.network.dao.PhysicalNetworkServiceProviderDao;
import com.cloud.network.dao.VirtualRouterProviderDao;
import com.cloud.network.element.IpDeployer;
import com.cloud.network.element.LoadBalancingServiceProvider;
import com.cloud.network.element.VirtualRouterElement;
import com.cloud.network.element.VirtualRouterProviderVO;
import com.cloud.network.lb.LoadBalancingRule;
import com.cloud.network.router.VirtualRouter;
import com.cloud.network.router.VirtualRouter.Role;
import com.cloud.network.rules.LoadBalancerContainer;
import com.cloud.network.rules.LoadBalancerContainer.Scheme;
import com.cloud.offering.NetworkOffering;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.User;
import com.cloud.utils.component.AdapterBase;
import com.cloud.utils.db.EntityManager;
import com.cloud.utils.db.QueryBuilder;
import com.cloud.utils.db.SearchCriteria.Op;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.Ip;
import com.cloud.vm.DomainRouterVO;
import com.cloud.vm.NicProfile;
import com.cloud.vm.ReservationContext;
import com.cloud.vm.VirtualMachine;
import com.cloud.vm.VirtualMachineProfile;
import com.cloud.vm.dao.DomainRouterDao;
import com.cloud.network.router.NetworkHelper;
import org.springframework.stereotype.Component;
@Component
public class InternalLoadBalancerElement extends AdapterBase implements LoadBalancingServiceProvider, InternalLoadBalancerElementService, IpDeployer {
protected static final Map<Service, Map<Capability, String>> capabilities = setCapabilities();
private static InternalLoadBalancerElement internalLbElement = null;
@Inject
NetworkModel _ntwkModel;
@Inject
NetworkServiceMapDao _ntwkSrvcDao;
@Inject
DomainRouterDao _routerDao;
@Inject
VirtualRouterProviderDao _vrProviderDao;
@Inject
PhysicalNetworkServiceProviderDao _pNtwkSvcProviderDao;
@Inject
InternalLoadBalancerVMManager _internalLbMgr;
@Inject
ConfigurationManager _configMgr;
@Inject
AccountManager _accountMgr;
@Inject
ApplicationLoadBalancerRuleDao _appLbDao;
@Inject
EntityManager _entityMgr;
@Autowired
@Qualifier("networkHelper")
protected NetworkHelper _networkHelper;
public InternalLoadBalancerElement() {
}
private boolean canHandle(Network config, Scheme lbScheme) {
//works in Advance zone only
DataCenter dc = _entityMgr.findById(DataCenter.class, config.getDataCenterId());
if (dc.getNetworkType() != NetworkType.Advanced) {
logger.trace("Not hanling zone of network type " + dc.getNetworkType());
return false;
}
if (config.getGuestType() != Network.GuestType.Isolated || config.getTrafficType() != TrafficType.Guest) {
logger.trace("Not handling network with Type " + config.getGuestType() + " and traffic type " + config.getTrafficType());
return false;
}
Map<Capability, String> lbCaps = getCapabilities().get(Service.Lb);
if (!lbCaps.isEmpty()) {
String schemeCaps = lbCaps.get(Capability.LbSchemes);
if (schemeCaps != null && lbScheme != null) {
if (!schemeCaps.contains(lbScheme.toString())) {
logger.debug("Scheme " + lbScheme.toString() + " is not supported by the provider " + getName());
return false;
}
}
}
if (!_ntwkModel.isProviderSupportServiceInNetwork(config.getId(), Service.Lb, getProvider())) {
logger.trace("Element " + getProvider().getName() + " doesn't support service " + Service.Lb + " in the network " + config);
return false;
}
return true;
}
@Override
public Map<Service, Map<Capability, String>> getCapabilities() {
return capabilities;
}
@Override
public Provider getProvider() {
return Provider.InternalLbVm;
}
@Override
public boolean implement(Network network, NetworkOffering offering, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException,
ResourceUnavailableException, InsufficientCapacityException {
if (!canHandle(network, null)) {
logger.trace("No need to implement " + getName());
return true;
}
return implementInternalLbVms(network, dest);
}
@Override
public boolean prepare(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context)
throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException {
if (!canHandle(network, null)) {
logger.trace("No need to prepare " + getName());
return true;
}
if (vm.getType() == VirtualMachine.Type.User) {
return implementInternalLbVms(network, dest);
}
return true;
}
protected boolean implementInternalLbVms(Network network, DeployDestination dest) throws ResourceUnavailableException {
//1) Get all the Ips from the network having LB rules assigned
List<String> ips = _appLbDao.listLbIpsBySourceIpNetworkIdAndScheme(network.getId(), Scheme.Internal);
//2) Start internal lb vms for the ips having active rules
for (String ip : ips) {
Ip sourceIp = new Ip(ip);
long active = _appLbDao.countActiveBySourceIp(sourceIp, network.getId());
if (active > 0) {
logger.debug("Have to implement internal lb vm for source ip " + sourceIp + " as a part of network " + network + " implement as there are " + active +
" internal lb rules exist for this ip");
List<? extends VirtualRouter> internalLbVms;
try {
internalLbVms = _internalLbMgr.deployInternalLbVm(network, sourceIp, dest, _accountMgr.getAccount(network.getAccountId()), null);
} catch (InsufficientCapacityException e) {
logger.warn("Failed to deploy element " + getName() + " for ip " + sourceIp + " due to:", e);
return false;
} catch (ConcurrentOperationException e) {
logger.warn("Failed to deploy element " + getName() + " for ip " + sourceIp + " due to:", e);
return false;
}
if (internalLbVms == null || internalLbVms.isEmpty()) {
throw new ResourceUnavailableException("Can't deploy " + getName() + " to handle LB rules", DataCenter.class, network.getDataCenterId());
}
}
}
return true;
}
@Override
public boolean release(Network network, NicProfile nic, VirtualMachineProfile vm, ReservationContext context) throws ConcurrentOperationException,
ResourceUnavailableException {
return true;
}
@Override
public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException {
List<? extends VirtualRouter> internalLbVms = _routerDao.listByNetworkAndRole(network.getId(), Role.INTERNAL_LB_VM);
if (internalLbVms == null || internalLbVms.isEmpty()) {
return true;
}
boolean result = true;
for (VirtualRouter internalLbVm : internalLbVms) {
result = result && _internalLbMgr.destroyInternalLbVm(internalLbVm.getId(), context.getAccount(), context.getCaller().getId());
if (cleanup) {
if (!result) {
logger.warn("Failed to stop internal lb element " + internalLbVm + ", but would try to process clean up anyway.");
}
result = (_internalLbMgr.destroyInternalLbVm(internalLbVm.getId(), context.getAccount(), context.getCaller().getId()));
if (!result) {
logger.warn("Failed to clean up internal lb element " + internalLbVm);
}
}
}
return result;
}
@Override
public boolean destroy(Network network, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException {
List<? extends VirtualRouter> internalLbVms = _routerDao.listByNetworkAndRole(network.getId(), Role.INTERNAL_LB_VM);
if (internalLbVms == null || internalLbVms.isEmpty()) {
return true;
}
boolean result = true;
for (VirtualRouter internalLbVm : internalLbVms) {
result = result && (_internalLbMgr.destroyInternalLbVm(internalLbVm.getId(), context.getAccount(), context.getCaller().getId()));
}
return result;
}
@Override
public boolean isReady(PhysicalNetworkServiceProvider provider) {
VirtualRouterProviderVO element = _vrProviderDao.findByNspIdAndType(provider.getId(), Type.InternalLbVm);
if (element == null) {
return false;
}
return element.isEnabled();
}
@Override
public boolean shutdownProviderInstances(PhysicalNetworkServiceProvider provider, ReservationContext context) throws ConcurrentOperationException,
ResourceUnavailableException {
VirtualRouterProviderVO element = _vrProviderDao.findByNspIdAndType(provider.getId(), Type.InternalLbVm);
if (element == null) {
return true;
}
long elementId = element.getId();
List<DomainRouterVO> internalLbVms = _routerDao.listByElementId(elementId);
boolean result = true;
for (DomainRouterVO internalLbVm : internalLbVms) {
result = result && (_internalLbMgr.destroyInternalLbVm(internalLbVm.getId(), context.getAccount(), context.getCaller().getId()));
}
_vrProviderDao.remove(elementId);
return result;
}
@Override
public boolean canEnableIndividualServices() {
return true;
}
@Override
public boolean verifyServicesCombination(Set<Service> services) {
return true;
}
@Override
public IpDeployer getIpDeployer(Network network) {
return this;
}
@Override
public boolean applyLBRules(Network network, List<LoadBalancingRule> rules) throws ResourceUnavailableException {
//1) Get Internal LB VMs to destroy
Set<Ip> vmsToDestroy = getVmsToDestroy(network, rules);
//2) Get rules to apply
Map<Ip, List<LoadBalancingRule>> rulesToApply = getLbRulesToApply(rules);
logger.debug("Applying " + rulesToApply.size() + " on element " + getName());
for (Ip sourceIp : vmsToDestroy) {
//2.1 Destroy internal lb vm
List<? extends VirtualRouter> vms = _internalLbMgr.findInternalLbVms(network.getId(), sourceIp);
if (vms.size() > 0) {
//only one internal lb per IP exists
try {
logger.debug(String.format("Destroying internal lb vm for ip %s as all the rules for this vm are in Revoke state", sourceIp.addr()));
return _internalLbMgr.destroyInternalLbVm(vms.get(0).getId(), _accountMgr.getAccount(Account.ACCOUNT_ID_SYSTEM),
_accountMgr.getUserIncludingRemoved(User.UID_SYSTEM).getId());
} catch (ConcurrentOperationException e) {
logger.warn(String.format("Failed to apply lb rule(s) for ip %s on the element %s due to: ", sourceIp.addr(), getName()), e);
return false;
}
}
rulesToApply.remove(sourceIp);
}
for (Map.Entry<Ip,List<LoadBalancingRule>> entry : rulesToApply.entrySet()) {
Ip sourceIp = entry.getKey();
//2.2 Start Internal LB vm per IP address
List<? extends VirtualRouter> internalLbVms;
try {
DeployDestination dest = new DeployDestination(_entityMgr.findById(DataCenter.class, network.getDataCenterId()), null, null, null);
internalLbVms = _internalLbMgr.deployInternalLbVm(network, sourceIp, dest, _accountMgr.getAccount(network.getAccountId()), null);
} catch (InsufficientCapacityException e) {
logger.warn(String.format("Failed to apply lb rule(s) for ip %s on the element %s due to: ", sourceIp.addr(), getName()), e);
return false;
} catch (ConcurrentOperationException e) {
logger.warn(String.format("Failed to apply lb rule(s) for ip %s on the element %s due to: ", sourceIp.addr(), getName()), e);
return false;
}
if (internalLbVms == null || internalLbVms.isEmpty()) {
throw new ResourceUnavailableException("Can't find/deploy internal lb vm to handle LB rules",
DataCenter.class, network.getDataCenterId());
}
//2.3 Apply Internal LB rules on the VM
if (!_internalLbMgr.applyLoadBalancingRules(network, entry.getValue(), internalLbVms)) {
throw new CloudRuntimeException("Failed to apply load balancing rules for ip " + sourceIp.addr() +
" in network " + network.getId() + " on element " + getName());
}
}
return true;
}
protected Map<Ip, List<LoadBalancingRule>> getLbRulesToApply(List<LoadBalancingRule> rules) {
//Group rules by the source ip address as NetworkManager always passes the entire network lb config to the element
Map<Ip, List<LoadBalancingRule>> rulesToApply = groupBySourceIp(rules);
return rulesToApply;
}
protected Set<Ip> getVmsToDestroy(Network network, List<LoadBalancingRule> rules) {
//1) Group rules by the source ip address as NetworkManager always passes the entire network lb config to the element
Set<Ip> lbPublicIps = new HashSet<Ip>();
Set<Ip> vmsToDestroy = new HashSet<Ip>();
for (LoadBalancingRule rule : rules) {
lbPublicIps.add(rule.getSourceIp());
}
for (Ip sourceIp : lbPublicIps) {
//2) Check if there are non revoked rules for the source ip address
if (_appLbDao.countBySourceIpAndNotRevoked(sourceIp, network.getId()) == 0) {
logger.debug("Have to destroy internal lb vm for source ip " + sourceIp + " as it has 0 rules in non-Revoke state");
vmsToDestroy.add(sourceIp);
}
}
return vmsToDestroy;
}
protected Map<Ip, List<LoadBalancingRule>> groupBySourceIp(List<LoadBalancingRule> rules) {
Map<Ip, List<LoadBalancingRule>> groupedRules = new HashMap<Ip, List<LoadBalancingRule>>();
for (LoadBalancingRule rule : rules) {
if (rule.getDestinations() != null && !rule.getDestinations().isEmpty()) {
Ip sourceIp = rule.getSourceIp();
if (!groupedRules.containsKey(sourceIp)) {
groupedRules.put(sourceIp, null);
}
List<LoadBalancingRule> rulesToApply = groupedRules.get(sourceIp);
if (rulesToApply == null) {
rulesToApply = new ArrayList<LoadBalancingRule>();
}
rulesToApply.add(rule);
groupedRules.put(sourceIp, rulesToApply);
} else {
logger.debug("Internal lb rule " + rule + " doesn't have any vms assigned, skipping");
}
}
return groupedRules;
}
@Override
public boolean validateLBRule(Network network, LoadBalancingRule rule) {
List<LoadBalancingRule> rules = new ArrayList<LoadBalancingRule>();
rules.add(rule);
if (canHandle(network, rule.getScheme())) {
List<DomainRouterVO> routers = _routerDao.listByNetworkAndRole(network.getId(), Role.INTERNAL_LB_VM);
if (routers == null || routers.isEmpty()) {
return true;
}
return _networkHelper.validateHAProxyLBRule(rule);
}
return true;
}
@Override
public List<LoadBalancerTO> updateHealthChecks(Network network, List<LoadBalancingRule> lbrules) {
return null;
}
@Override
public boolean handlesOnlyRulesInTransitionState() {
return false;
}
private static Map<Service, Map<Capability, String>> setCapabilities() {
Map<Service, Map<Capability, String>> capabilities = new HashMap<Service, Map<Capability, String>>();
// Set capabilities for LB service
Map<Capability, String> lbCapabilities = new HashMap<Capability, String>();
lbCapabilities.put(Capability.SupportedLBAlgorithms, "roundrobin,leastconn,source");
lbCapabilities.put(Capability.SupportedLBIsolation, "dedicated");
lbCapabilities.put(Capability.SupportedProtocols, "tcp, udp");
lbCapabilities.put(Capability.SupportedStickinessMethods, VirtualRouterElement.getHAProxyStickinessCapability());
lbCapabilities.put(Capability.LbSchemes, LoadBalancerContainer.Scheme.Internal.toString());
capabilities.put(Service.Lb, lbCapabilities);
return capabilities;
}
@Override
public List<Class<?>> getCommands() {
List<Class<?>> cmdList = new ArrayList<Class<?>>();
cmdList.add(CreateInternalLoadBalancerElementCmd.class);
cmdList.add(ConfigureInternalLoadBalancerElementCmd.class);
cmdList.add(ListInternalLoadBalancerElementsCmd.class);
return cmdList;
}
@Override
public VirtualRouterProvider configureInternalLoadBalancerElement(long id, boolean enable) {
VirtualRouterProviderVO element = _vrProviderDao.findById(id);
if (element == null || element.getType() != Type.InternalLbVm) {
throw new InvalidParameterValueException("Can't find " + getName() + " element with network service provider id " + id + " to be used as a provider for " +
getName());
}
element.setEnabled(enable);
element = _vrProviderDao.persist(element);
return element;
}
@Override
public VirtualRouterProvider addInternalLoadBalancerElement(long ntwkSvcProviderId) {
VirtualRouterProviderVO element = _vrProviderDao.findByNspIdAndType(ntwkSvcProviderId, Type.InternalLbVm);
if (element != null) {
logger.debug("There is already an " + getName() + " with service provider id " + ntwkSvcProviderId);
return null;
}
PhysicalNetworkServiceProvider provider = _pNtwkSvcProviderDao.findById(ntwkSvcProviderId);
if (provider == null || !provider.getProviderName().equalsIgnoreCase(getName())) {
throw new InvalidParameterValueException("Invalid network service provider is specified");
}
element = new VirtualRouterProviderVO(ntwkSvcProviderId, Type.InternalLbVm);
element = _vrProviderDao.persist(element);
return element;
}
@Override
public VirtualRouterProvider getInternalLoadBalancerElement(long id) {
VirtualRouterProvider provider = _vrProviderDao.findById(id);
if (provider == null || provider.getType() != Type.InternalLbVm) {
throw new InvalidParameterValueException("Unable to find " + getName() + " by id");
}
return provider;
}
@Override
public List<? extends VirtualRouterProvider> searchForInternalLoadBalancerElements(Long id, Long ntwkSvsProviderId, Boolean enabled) {
QueryBuilder<VirtualRouterProviderVO> sc = QueryBuilder.create(VirtualRouterProviderVO.class);
if (id != null) {
sc.and(sc.entity().getId(), Op.EQ, id);
}
if (ntwkSvsProviderId != null) {
sc.and(sc.entity().getNspId(), Op.EQ, ntwkSvsProviderId);
}
if (enabled != null) {
sc.and(sc.entity().isEnabled(), Op.EQ, enabled);
}
//return only Internal LB elements
sc.and(sc.entity().getType(), Op.EQ, VirtualRouterProvider.Type.InternalLbVm);
return sc.list();
}
@Override
public Type getProviderType() {
return Type.InternalLbVm;
}
@Override
public boolean applyIps(Network network, List<? extends PublicIpAddress> ipAddress, Set<Service> services) throws ResourceUnavailableException {
//do nothing here; this element just has to extend the ip deployer
//as the LB service implements IPDeployerRequester
return true;
}
}