/************************************************************************* * Copyright 2009-2015 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. ************************************************************************/ package com.eucalyptus.loadbalancing.dns; import static com.eucalyptus.loadbalancing.activities.LoadBalancerAutoScalingGroup.LoadBalancerAutoScalingGroupEntityTransform.*; import static com.eucalyptus.loadbalancing.activities.LoadBalancerServoInstance.LoadBalancerServoInstanceCoreView; import static com.eucalyptus.util.dns.DnsResolvers.DnsRequest; import static com.eucalyptus.util.dns.DnsResolvers.DnsResponse; import java.net.InetAddress; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.apache.log4j.Logger; import org.xbill.DNS.Name; import org.xbill.DNS.Record; import com.eucalyptus.bootstrap.Bootstrap; import com.eucalyptus.configurable.ConfigurableClass; import com.eucalyptus.configurable.ConfigurableField; import com.eucalyptus.entities.Entities; import com.eucalyptus.entities.TransactionResource; import com.eucalyptus.loadbalancing.LoadBalancer; import com.eucalyptus.loadbalancing.LoadBalancerDnsRecord; import com.eucalyptus.loadbalancing.LoadBalancers; import com.eucalyptus.loadbalancing.activities.LoadBalancerAutoScalingGroup.LoadBalancerAutoScalingGroupCoreView; import com.eucalyptus.util.Pair; import com.eucalyptus.util.dns.DnsResolvers; import com.eucalyptus.util.dns.DomainNameRecords; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.net.InetAddresses; /** * */ @ConfigurableClass( root = "services.loadbalancing", description = "Parameters controlling loadbalancing" ) public class LoadBalancerResolver extends DnsResolvers.DnsResolver { private static final Logger logger = Logger.getLogger( LoadBalancerResolver.class ); private static final int QUERY_ANSWER_EXPIRE_AFTER_SEC = 5; private static final LoadingCache<Name, List<String>> cachedAnswers = CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(QUERY_ANSWER_EXPIRE_AFTER_SEC, TimeUnit.SECONDS) .build(new CacheLoader<Name, List<String>> () { @Override public List<String> load(final Name name) throws Exception { return resolveName(name); } }); /// the LoadingCache is used to set the hard limit on memory usage private static final LoadingCache<Name, IpPermutation> nameToIpPermutations = CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterAccess(1, TimeUnit.HOURS) .build(new CacheLoader<Name, IpPermutation> () { @Override public IpPermutation load(Name name) throws Exception { final List<String> ips = cachedAnswers.get(name); return new IpPermutation(ips); } }); @ConfigurableField( description = "Enable the load balancing DNS resolver. Note: dns.enable must also be 'true'", initial = "true" ) public static Boolean dns_resolver_enabled = Boolean.TRUE; @Override public boolean checkAccepts( final DnsRequest request ) { final Record query = request.getQuery( ); if ( !Bootstrap.isOperational( ) || !dns_resolver_enabled ) { return false; } else if ( query.getName( ).subdomain( LoadBalancerDomainName.getLoadBalancerSubdomain() ) ) { return true; } return false; } private static List<String> resolveName(final Name name) { final Name hostName = name.relativize( LoadBalancerDomainName.getLoadBalancerSubdomain( ) ); final Optional<LoadBalancerDomainName> domainName = LoadBalancerDomainName.findMatching( hostName ); final Set<String> ips = Sets.newTreeSet( ); if ( domainName.isPresent( ) ) { final Pair<String,String> accountNamePair = domainName.get( ).toScopedLoadBalancerName( hostName ); try ( final TransactionResource tx = Entities.transactionFor( LoadBalancer.class ) ) { final LoadBalancer loadBalancer = LoadBalancers.getLoadbalancerCaseInsensitive( accountNamePair.getLeft( ), accountNamePair.getRight( ) ); final Predicate<LoadBalancerServoInstanceCoreView> canResolve = new Predicate<LoadBalancerServoInstanceCoreView>(){ @Override public boolean apply(LoadBalancerServoInstanceCoreView arg0) { return arg0.canResolveDns(); } }; final List<LoadBalancerServoInstanceCoreView> servos = Lists.newArrayList(); for(final LoadBalancerAutoScalingGroupCoreView group : loadBalancer.getAutoScaleGroups()) { servos.addAll(INSTANCE.apply( group ).getServos()); } final Function<LoadBalancerServoInstanceCoreView,String> ipExtractor = loadBalancer.getScheme( ) == LoadBalancer.Scheme.Internal ? LoadBalancerServoInstanceCoreView.privateIp( ) : LoadBalancerServoInstanceCoreView.address( ); Iterables.addAll( ips, Iterables.transform( Collections2.filter( servos, canResolve), ipExtractor ) ); } } List<String> ipList = Lists.newArrayList(ips); Collections.sort(ipList); return ipList; } private static class IpPermutation { private List<String> ips = null; private List<List<String>> ipPermutations = null; private int idx = 0; private IpPermutation(final List<String> ips) { this.ips = ips; ipPermutations = Lists.newArrayList(Collections2.permutations(ips)); Collections.shuffle(ipPermutations); } public synchronized List<String> getNext() { try{ final List<String> next = ipPermutations.get(idx); if (++idx >= ipPermutations.size()) idx = 0; return next; }catch(final Exception ex) { return Lists.newArrayList(); } } public boolean isPermuatationFrom(final List<String> ips) { return this.ips.equals(ips); } } private static List<String> getIps (final Name name) throws ExecutionException{ final List<String> ips = cachedAnswers.get(name); final IpPermutation old = nameToIpPermutations.get(name); if(!old.isPermuatationFrom(ips)) nameToIpPermutations.invalidate(name); return nameToIpPermutations.get(name).getNext(); } @Override public DnsResponse lookupRecords( final DnsRequest request ) { final Record query = request.getQuery( ); try { final Name name = query.getName( ); final List<String> ips = getIps(name); final List<Record> records = Lists.newArrayList( ); for ( String ip : ips ) { final InetAddress inetAddress = InetAddresses.forString( ip ); records.add( DomainNameRecords.addressRecord( name, inetAddress, LoadBalancerDnsRecord.getLoadbalancerTTL( ) ) ); } if(DnsResolvers.RequestType.A.apply( query )) return DnsResponse.forName( name ).answer( records ); else return DnsResponse.forName( name ).answer(Lists.<Record>newArrayList()); } catch ( Exception ex ) { logger.debug( ex ); } return DnsResponse.forName( query.getName( ) ).nxdomain( ); } }