All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.eureka2.interests.host.DnsChangeNotificationSource Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014 Netflix, Inc.
 *
 * Licensed 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.netflix.eureka2.interests.host;

import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import com.netflix.eureka2.interests.ChangeNotification;
import com.netflix.eureka2.interests.ChangeNotification.Kind;
import com.netflix.eureka2.interests.ChangeNotificationSource;
import com.netflix.eureka2.interests.Interest;
import com.netflix.eureka2.utils.rx.ResourceObservable;
import com.netflix.eureka2.utils.rx.ResourceObservable.ResourceLoader;
import com.netflix.eureka2.utils.rx.ResourceObservable.ResourceLoaderException;
import com.netflix.eureka2.utils.rx.ResourceObservable.ResourceUpdate;
import rx.Observable;
import rx.Scheduler;
import rx.schedulers.Schedulers;

/**
 * Change notification from DNS lookup.
 *
 * TODO: snapshot/interest based filtering not implemented yet.
 *
 * @author Tomasz Bak
 */
public class DnsChangeNotificationSource implements ChangeNotificationSource {

    private static final long DNS_LOOKUP_INTERVAL = 30;
    private static final long IDLE_TIMEOUT = 300;

    private final String domainName;
    private final Observable> resolverObservable;

    public DnsChangeNotificationSource(String domainName) {
        this(domainName, DNS_LOOKUP_INTERVAL, IDLE_TIMEOUT, TimeUnit.SECONDS, Schedulers.io());
    }

    public DnsChangeNotificationSource(String domainName, long reloadInterval, TimeUnit timeUnit) {
        this(domainName, reloadInterval, -1, timeUnit, Schedulers.io());
    }

    public DnsChangeNotificationSource(String domainName, long reloadInterval, long idleTimeout, TimeUnit reloadUnit,
                                       Scheduler scheduler) {
        this.domainName = domainName;
        if ("localhost".equals(domainName)) {
            this.resolverObservable = Observable.just(new ChangeNotification<>(Kind.Add, domainName));
        } else {
            this.resolverObservable = ResourceObservable.fromResource(new DnsResolverTask(), reloadInterval,
                    idleTimeout, reloadUnit, scheduler);
        }
    }

    @Override
    public Observable forSnapshot(Interest interest) {
        throw new IllegalStateException("operation not supported");
    }

    @Override
    public Observable> forInterest(Interest interest) {
        return resolverObservable;
    }

    class DnsResolverTask implements ResourceLoader> {

        private boolean succeededOnce;

        @Override
        public ResourceUpdate> reload(Set> currentSnapshot) {
            try {
                Set> newAddresses = resolveServerDN();
                succeededOnce = true;
                return new ResourceUpdate<>(newAddresses, cancellationSet(currentSnapshot, newAddresses));
            } catch (NamingException e) {
                if (succeededOnce) {
                    throw new ResourceLoaderException("DNS failure on subsequent access", true, e);
                } else {
                    throw new ResourceLoaderException("Cannot resolve DNS entry on startup", false, e);
                }
            }
        }

        private Set> cancellationSet(Set> currentSnapshot,
                                                                Set> newAddresses) {
            Set> cancelled = new HashSet<>();
            for (ChangeNotification entry : currentSnapshot) {
                if (!newAddresses.contains(entry)) {
                    cancelled.add(entry);
                }
            }
            return cancelled;
        }

        private Set> resolveServerDN() throws NamingException {
            Hashtable env = new Hashtable();
            env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
            DirContext dirContext = new InitialDirContext(env);
            try {
                return resolveName(dirContext, domainName);
            } finally {
                dirContext.close();
            }
        }

        private Set> resolveName(DirContext dirContext, String targetDN) throws NamingException {
            while (true) {
                Attributes attrs = dirContext.getAttributes(targetDN, new String[]{"A", "CNAME"});
                Set> addresses = toSetOfServerEntries(attrs, "A");
                if (!addresses.isEmpty()) {
                    return addresses;
                }
                Set aliases = toSetOfString(attrs, "CNAME");
                if (aliases != null && !aliases.isEmpty()) {
                    targetDN = aliases.iterator().next();
                    continue;
                }
                return addresses;
            }
        }

        private Set toSetOfString(Attributes attrs, String attrName) throws NamingException {
            Attribute attr = attrs.get(attrName);
            if (attr == null) {
                return Collections.emptySet();
            }
            Set resultSet = new HashSet<>();
            NamingEnumeration it = attr.getAll();
            while (it.hasMore()) {
                Object value = it.next();
                resultSet.add(value.toString());
            }
            return resultSet;
        }

        private Set> toSetOfServerEntries(Attributes attrs, String attrName) throws NamingException {
            Attribute attr = attrs.get(attrName);
            if (attr == null) {
                return Collections.emptySet();
            }
            Set> resultSet = new HashSet<>();
            NamingEnumeration it = attr.getAll();
            while (it.hasMore()) {
                Object value = it.next();
                resultSet.add(new ChangeNotification<>(Kind.Add, (String) value));
            }
            return resultSet;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy