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

org.apache.brooklyn.entity.dns.AbstractGeoDnsServiceImpl Maven / Gradle / Ivy

/*
 * 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.brooklyn.entity.dns;

import static com.google.common.base.Preconditions.checkNotNull;

import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.Group;
import org.apache.brooklyn.api.policy.PolicySpec;
import org.apache.brooklyn.api.sensor.Sensor;
import org.apache.brooklyn.core.entity.AbstractEntity;
import org.apache.brooklyn.core.entity.Attributes;
import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic;
import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic.ServiceNotUpLogic;
import org.apache.brooklyn.core.location.geo.HostGeoInfo;
import org.apache.brooklyn.entity.group.AbstractMembershipTrackingPolicy;
import org.apache.brooklyn.entity.group.DynamicGroup;
import org.apache.brooklyn.entity.webapp.WebAppService;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.core.flags.SetFromFlag;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.net.Networking;
import org.apache.brooklyn.util.time.Duration;
import org.apache.brooklyn.util.time.Time;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;

public abstract class AbstractGeoDnsServiceImpl extends AbstractEntity implements AbstractGeoDnsService {
    private static final Logger log = LoggerFactory.getLogger(AbstractGeoDnsService.class);

    @SetFromFlag
    protected Group targetEntityProvider;
    protected AbstractMembershipTrackingPolicy tracker;
    /** Guards access to {@link #tracker}. */
    protected final Object trackerLock = new Object[0];

    protected Map targetHosts = Collections.synchronizedMap(new LinkedHashMap());

    // We complain (at debug) when we encounter a target entity for whom we can't derive hostname/ip information; 
    // this is the commonest case for the transient condition between the time the entity is created and the time 
    // it is started (at which point the location is specified). This set contains those entities we've complained 
    // about already, to avoid repetitive logging.
    transient protected Set entitiesWithoutHostname = new HashSet();

    // We complain (at info/warn) when we encounter a target entity for whom we can't derive geo information, even 
    // when hostname/ip is known. This set contains those entities we've complained about already, to avoid repetitive 
    // logging.
    transient protected Set entitiesWithoutGeoInfo = new HashSet();

    public AbstractGeoDnsServiceImpl() {
        super();
    }

    @Override
    public void init() {
        super.init();
        Group initialProvider = config().get(ENTITY_PROVIDER);
        if (initialProvider != null) {
            setTargetEntityProvider(initialProvider);
        }
    }

    @Override
    public Map getTargetHosts() {
        return targetHosts;
    }

    @Override
    public void onManagementStarted() {
        super.onManagementStarted();
        startTracker();
    }

    @Override
    public void onManagementStopped() {
        endTracker();
        super.onManagementStopped();
    }

    @Override
    public void destroy() {
        setServiceState(Lifecycle.DESTROYED);
        super.destroy();
    }

    @Override
    public void setServiceState(Lifecycle state) {
        sensors().set(HOSTNAME, getHostname());
        ServiceStateLogic.setExpectedState(this, state);
        if (state==Lifecycle.RUNNING)
            ServiceNotUpLogic.clearNotUpIndicator(this, SERVICE_STATE_ACTUAL);
        else
            ServiceNotUpLogic.updateNotUpIndicator(this, SERVICE_STATE_ACTUAL, "Not in RUNNING state");
    }

    @Override
    public void setTargetEntityProvider(final Group entityProvider) {
        this.targetEntityProvider = checkNotNull(entityProvider, "targetEntityProvider");
        startTracker();
    }

    /** should set up so these hosts are targeted, and setServiceState appropriately */
    protected abstract void reconfigureService(Collection targetHosts);

    /**
     * Start the tracker.
     * 

* Subclasses should take care to synchronize on {@link #trackerLock}. */ protected void startTracker() { synchronized (trackerLock) { if (targetEntityProvider == null || !getManagementSupport().isDeployed()) { log.debug("Tracker for " + this + " not yet active: " + targetEntityProvider + " / " + getManagementContext()); return; } endTracker(); ImmutableSet.Builder> sensorsToTrack = ImmutableSet.>builder().add( HOSTNAME, ADDRESS, Attributes.MAIN_URI, WebAppService.ROOT_URL); // Don't subscribe to lifecycle events if entities will be included regardless of their status. if (Boolean.TRUE.equals(config().get(FILTER_FOR_RUNNING))) { sensorsToTrack.add(Attributes.SERVICE_STATE_ACTUAL); } log.debug("Initializing tracker for " + this + ", following " + targetEntityProvider); tracker = policies().add(PolicySpec.create(MemberTrackingPolicy.class) .displayName("GeoDNS targets tracker") .configure(AbstractMembershipTrackingPolicy.SENSORS_TO_TRACK, sensorsToTrack.build()) .configure(AbstractMembershipTrackingPolicy.GROUP, targetEntityProvider)); refreshGroupMembership(); } } /** * End the tracker. *

* Subclasses should take care to synchronize on {@link #trackerLock}. */ protected void endTracker() { synchronized (trackerLock) { if (tracker == null || targetEntityProvider == null) return; policies().remove(tracker); tracker = null; } } public static class MemberTrackingPolicy extends AbstractMembershipTrackingPolicy { @Override protected void onEntityEvent(EventType type, Entity entity) { ((AbstractGeoDnsServiceImpl)super.entity).refreshGroupMembership(); } } @Override public abstract String getHostname(); long lastUpdate = -1; // TODO: remove group member polling once locations can be determined via subscriptions protected void refreshGroupMembership() { try { if (log.isDebugEnabled()) log.debug("GeoDns {} refreshing targets", this); if (targetEntityProvider == null) return; if (targetEntityProvider instanceof DynamicGroup) ((DynamicGroup) targetEntityProvider).rescanEntities(); Set pool = MutableSet.copyOf(targetEntityProvider instanceof Group ? targetEntityProvider.getMembers(): targetEntityProvider.getChildren()); if (log.isDebugEnabled()) log.debug("GeoDns {} refreshing targets, pool now {}", this, pool); boolean changed = false; boolean filterForRunning = Boolean.TRUE.equals(config().get(FILTER_FOR_RUNNING)); Set previousOnes = MutableSet.copyOf(targetHosts.keySet()); for (Entity e: pool) { if (!filterForRunning || Lifecycle.RUNNING.equals(e.sensors().get(Attributes.SERVICE_STATE_ACTUAL))) { previousOnes.remove(e); changed |= addTargetHost(e); } } // anything left in previousOnes is no longer applicable for (Entity e: previousOnes) { changed |= removeTargetHost(e, false); } // do a periodic full update hourly once we are active (the latter is probably not needed) if (changed || (lastUpdate > 0 && Time.hasElapsedSince(lastUpdate, Duration.ONE_HOUR))) { update(); } } catch (Exception e) { log.error("Problem refreshing group membership: "+e, e); } } /** * Adds this host, if it is absent or if its hostname has changed. *

* For whether to use hostname or ip, see config and attributes {@link AbstractGeoDnsService#USE_HOSTNAMES}, * {@link Attributes#HOSTNAME} and {@link Attributes#ADDRESS} (via {@link #inferHostname(Entity)} and {@link #inferIp(Entity)}. * Note that the "hostname" could in fact be an IP address, if {@link #inferHostname(Entity)} returns an IP! *

* TODO in a future release, we may change this to explicitly set the sensor(s) to look at on the entity, and * be stricter about using them in order. * * @return true if host is added or changed */ protected boolean addTargetHost(Entity entity) { try { HostGeoInfo oldGeo = targetHosts.get(entity); String hostname = inferHostname(entity); String ip = inferIp(entity); String addr = (getConfig(USE_HOSTNAMES) || ip == null) ? hostname : ip; if (addr==null) addr = ip; if (addr == null) { if (entitiesWithoutHostname.add(entity)) { log.debug("GeoDns ignoring {} (no hostname/ip/URL info yet available)", entity); } return false; } // prefer the geo from the entity (or location parent), but fall back to inferring // e.g. if it supplies a URL HostGeoInfo geo = HostGeoInfo.fromEntity(entity); if (geo==null) geo = inferHostGeoInfo(hostname, ip); if (Networking.isPrivateSubnet(addr) && ip!=null && !Networking.isPrivateSubnet(ip)) { // fix for #1216 log.debug("GeoDns using IP "+ip+" for "+entity+" as addr "+addr+" resolves to private subnet"); addr = ip; } if (Networking.isPrivateSubnet(addr)) { if (getConfig(INCLUDE_HOMELESS_ENTITIES)) { if (entitiesWithoutGeoInfo.add(entity)) { log.info("GeoDns including {}, even though {} is a private subnet (homeless entities included)", entity, addr); } } else { if (entitiesWithoutGeoInfo.add(entity)) { log.warn("GeoDns ignoring {} (private subnet detected for {})", entity, addr); } return false; } } if (geo == null) { if (getConfig(INCLUDE_HOMELESS_ENTITIES)) { if (entitiesWithoutGeoInfo.add(entity)) { log.info("GeoDns including {}, even though no geography info available for {})", entity, addr); } geo = HostGeoInfo.create(addr, "unknownLocation("+addr+")", 0, 0); } else { if (entitiesWithoutGeoInfo.add(entity)) { log.warn("GeoDns ignoring {} (no geography info available for {})", entity, addr); } return false; } } if (!addr.equals(geo.getAddress())) { // if the location provider did not have an address, create a new one with it geo = HostGeoInfo.create(addr, geo.displayName, geo.latitude, geo.longitude); } // If we already knew about it, and it hasn't changed, then nothing to do if (oldGeo != null && geo.getAddress().equals(oldGeo.getAddress())) { return false; } entitiesWithoutHostname.remove(entity); entitiesWithoutGeoInfo.remove(entity); log.info("GeoDns adding "+entity+" at "+geo+(oldGeo != null ? " (previously "+oldGeo+")" : "")); targetHosts.put(entity, geo); return true; } catch (Exception ee) { log.warn("GeoDns ignoring "+entity+" (error analysing location): "+ee, ee); return false; } } /** remove if host removed */ protected boolean removeTargetHost(Entity e, boolean doUpdate) { if (targetHosts.remove(e) != null) { log.info("GeoDns removing reference to {}", e); if (doUpdate) update(); return true; } return false; } protected void update() { lastUpdate = System.currentTimeMillis(); Map m; synchronized(targetHosts) { m = ImmutableMap.copyOf(targetHosts); } if (log.isDebugEnabled()) log.debug("Full update of "+this+" ("+m.size()+" target hosts)"); Map entityIdToAddress = Maps.newLinkedHashMap(); for (Map.Entry entry : m.entrySet()) { entityIdToAddress.put(entry.getKey().getId(), entry.getValue().address); } reconfigureService(new LinkedHashSet(m.values())); if (log.isDebugEnabled()) log.debug("Targets being set as "+entityIdToAddress); sensors().set(TARGETS, entityIdToAddress); } protected String inferHostname(Entity entity) { String hostname = entity.getAttribute(Attributes.HOSTNAME); URI url = entity.getAttribute(Attributes.MAIN_URI); if (url!=null) { try { URL u = url.toURL(); String hostname2 = u.getHost(); if (hostname==null) { if (!entitiesWithoutGeoInfo.contains(entity)) //don't log repeatedly log.warn("GeoDns "+this+" using URL {} to redirect to {} (HOSTNAME attribute is preferred, but not available)", url, entity); hostname = hostname2; } else if (!hostname.equals(hostname2)) { if (!entitiesWithoutGeoInfo.contains(entity)) //don't log repeatedly log.warn("GeoDns "+this+" URL {} of "+entity+" does not match advertised HOSTNAME {}; using hostname, not URL", url, hostname); } if (u.getPort() > 0 && u.getPort() != 80 && u.getPort() != 443) { if (!entitiesWithoutGeoInfo.contains(entity)) //don't log repeatedly log.warn("GeoDns "+this+" detected non-standard port in URL {} for {}; forwarding may not work", url, entity); } } catch (MalformedURLException e) { log.warn("Invalid URL {} for entity {} in {}", new Object[] {url, entity, this}); } } return hostname; } protected String inferIp(Entity entity) { return entity.getAttribute(Attributes.ADDRESS); } protected HostGeoInfo inferHostGeoInfo(String hostname, String ip) throws UnknownHostException { HostGeoInfo geoH = null; if (hostname != null) { try { // For some entities, the hostname can actually be an IP! Therefore use Networking.getInetAddressWithFixedName InetAddress addr = Networking.getInetAddressWithFixedName(hostname); geoH = HostGeoInfo.fromIpAddress(addr); } catch (RuntimeException e) { // Most likely caused by (a wrapped) UnknownHostException Exceptions.propagateIfFatal(e); if (ip == null) { if (log.isTraceEnabled()) log.trace("inferHostGeoInfo failing ("+Exceptions.getFirstInteresting(e)+"): hostname="+hostname+"; ip="+ip); throw e; } else { if (log.isTraceEnabled()) log.trace("GeoDns failed to infer GeoInfo from hostname {}; will try with IP {} ({})", new Object[] {hostname, ip, e}); } } } // Try IP address (prior to Mar 2014 we did not do this if USE_HOSTNAME was set but don't think that is desirable due to #1216) if (ip != null) { if (geoH == null) { InetAddress addr = Networking.getInetAddressWithFixedName(ip); geoH = HostGeoInfo.fromIpAddress(addr); if (log.isTraceEnabled()) log.trace("GeoDns inferred GeoInfo {} from ip {} (could not infer from hostname {})", new Object[] {geoH, ip, hostname}); } else { geoH = HostGeoInfo.create(ip, geoH.displayName, geoH.latitude, geoH.longitude); if (log.isTraceEnabled()) log.trace("GeoDns inferred GeoInfo {} from hostname {}; switching it to ip {}", new Object[] {geoH, hostname, ip}); } } else { if (log.isTraceEnabled()) log.trace("GeoDns inferred GeoInfo {} from hostname {}", geoH, hostname); } return geoH; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy