com.predic8.membrane.core.interceptor.apimanagement.apiconfig.EtcdRegistryApiConfig.java Source code

Java tutorial

Introduction

Here is the source code for com.predic8.membrane.core.interceptor.apimanagement.apiconfig.EtcdRegistryApiConfig.java

Source

/*
 * Copyright 2015 predic8 GmbH, www.predic8.com
 *    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.predic8.membrane.core.interceptor.apimanagement.apiconfig;

import com.predic8.membrane.annot.MCAttribute;
import com.predic8.membrane.annot.MCElement;
import com.predic8.membrane.core.Router;
import com.predic8.membrane.core.cloud.ExponentialBackoff;
import com.predic8.membrane.core.cloud.etcd.EtcdNodeInformation;
import com.predic8.membrane.core.cloud.etcd.EtcdRequest;
import com.predic8.membrane.core.cloud.etcd.EtcdResponse;
import com.predic8.membrane.core.config.spring.BaseLocationApplicationContext;
import com.predic8.membrane.core.interceptor.Interceptor;
import com.predic8.membrane.core.interceptor.administration.AdminConsoleInterceptor;
import com.predic8.membrane.core.interceptor.apimanagement.ApiManagementConfiguration;
import com.predic8.membrane.core.rules.AbstractServiceProxy;
import com.predic8.membrane.core.rules.Rule;
import com.predic8.membrane.core.rules.ServiceProxy;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.Lifecycle;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;

import java.io.IOException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;

@MCElement(name = "etcdRegistryApiConfig")
public class EtcdRegistryApiConfig implements Lifecycle, ApplicationContextAware, ApiConfig, DisposableBean {

    private static final Log log = LogFactory.getLog(EtcdRegistryApiConfig.class.getName());

    private ApplicationContext context;
    private ApiManagementConfiguration amc;
    private String url;
    private String membraneId;
    private String baseKeyPrefix = "/membrane/";
    private int ttl = 300;
    private int retryDelayMin = 10 * 1000;
    private int retryDelayMax = 10 * 60 * 1000;
    private double expDelayFactor = 2.0d;

    Thread publisher = new Thread(new Runnable() {

        int sleepTime = (ttl - 10) * 1000;

        @Override
        public void run() {
            try {
                boolean connectionLost = false;

                while (true) {
                    String baseKey = baseKeyPrefix + membraneId;
                    EtcdResponse respTTLDirRefresh = EtcdRequest.create(url, baseKey, "").refreshTTL(ttl)
                            .sendRequest();
                    if (!respTTLDirRefresh.is2XX()) {
                        log.warn("Could not contact etcd at " + url);
                        connectionLost = true;
                    }

                    Thread.sleep(sleepTime);
                    if (connectionLost) {
                        log.warn("Connection lost to etcd");
                        ExponentialBackoff.retryAfter(retryDelayMin, retryDelayMax, expDelayFactor,
                                "Republish from thread after failed ttl refresh", jobPublisher);
                    }
                }
            } catch (InterruptedException ignored) {
            }
        }
    });

    ExponentialBackoff.Job jobPublisher = new ExponentialBackoff.Job() {
        @Override
        public boolean run() throws Exception {
            return publishToEtcd();
        }
    };

    private boolean publishToEtcd() {
        String baseKey = baseKeyPrefix + membraneId;
        EtcdResponse respPublishApiUrl = EtcdRequest.create(url, baseKey, "/apiconfig").setValue("url", "")
                .sendRequest();
        if (!respPublishApiUrl.is2XX()) {
            System.out.println(respPublishApiUrl.getBody());
            return false;
        }
        EtcdResponse respPublishApiFingerprint = EtcdRequest.create(url, baseKey, "/apiconfig")
                .setValue("fingerprint", "").sendRequest();
        if (!respPublishApiFingerprint.is2XX()) {
            System.out.println(respPublishApiFingerprint.getBody());
            return false;
        }
        EtcdNodeInformation adminConsole = findAdminConsole();

        EtcdResponse respPublishEndpointName = EtcdRequest.create(url, baseKey, "/endpoint")
                .setValue("name", adminConsole.getName()).sendRequest();
        if (!respPublishEndpointName.is2XX()) {
            System.out.println(respPublishEndpointName.getBody());
            return false;
        }

        EtcdResponse respPublishEndpointHost = EtcdRequest.create(url, baseKey, "/endpoint")
                .setValue("host", adminConsole.getTargetHost()).sendRequest();
        if (!respPublishEndpointHost.is2XX()) {
            System.out.println(respPublishEndpointHost.getBody());
            return false;
        }

        EtcdResponse respPublishEndpointPort = EtcdRequest.create(url, baseKey, "/endpoint")
                .setValue("port", adminConsole.getTargetPort()).sendRequest();
        if (!respPublishEndpointPort.is2XX()) {
            System.out.println(respPublishEndpointPort.getBody());
            return false;
        }

        return true;
    }

    private EtcdNodeInformation findAdminConsole() {
        Object routerObj = context.getBean(Router.class);
        if (routerObj == null)
            throw new RuntimeException("Router not found, cannot publish admin console");
        Router router = (Router) routerObj;
        for (Rule r : router.getRuleManager().getRules()) {
            if (!(r instanceof AbstractServiceProxy))
                continue;

            for (Interceptor i : r.getInterceptors()) {
                if (i instanceof AdminConsoleInterceptor) {
                    String name = r.getName();
                    String host = ((ServiceProxy) r).getExternalHostname();
                    if (host == null)
                        host = getLocalHostname();
                    String port = Integer.toString(((AbstractServiceProxy) r).getPort());
                    EtcdNodeInformation node = new EtcdNodeInformation(null, null, host, port, name);
                    return node;

                }
            }
        }
        throw new RuntimeException(
                "Admin console not found but is needed. Add a service proxy with an admin console.");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    @Override
    public void start() {
        Object routerObj = context.getBean(Router.class);
        if (routerObj == null)
            throw new RuntimeException("Router cannot be found");
        Router router = (Router) routerObj;
        membraneId = router.getId();

        try {
            log.info("Started membrane publishing");
            ExponentialBackoff.retryAfter(retryDelayMin, retryDelayMax, expDelayFactor, "First publish from thread",
                    jobPublisher);
        } catch (InterruptedException ignored) {
        }
        initAmc();
        if (!publisher.isAlive()) {
            publisher.start();
        }
    }

    public void initAmc() {
        String workingDir = ((BaseLocationApplicationContext) context).getBaseLocation();
        String etcdUrlForAmc = null;
        URL u = null;
        try {
            u = new URL(getUrl());

        } catch (MalformedURLException e) {
            try {
                u = new URL("http://" + getUrl());
            } catch (MalformedURLException ignored) {
            }
        }
        if (u == null) {
            log.error("Url malformed: " + getUrl());
        }
        etcdUrlForAmc = "etcd://" + u.getHost() + ":" + u.getPort();
        amc = new ApiManagementConfiguration(workingDir, etcdUrlForAmc, membraneId);
    }

    @Override
    public void stop() {
        amc.shutdown();
    }

    @Override
    public boolean isRunning() {
        return false;
    }

    public String getUrl() {
        return url;
    }

    /**
     * @description the url to where details are published
     * @default http://localhost:4001
     */
    @MCAttribute
    public void setUrl(String url) {
        this.url = url;
    }

    public int getTtl() {
        return ttl;
    }

    /**
     * @description ttl in seconds
     * @default 300
     */
    @MCAttribute
    public void setTtl(int ttl) {
        this.ttl = ttl;
    }

    @Override
    public ApiManagementConfiguration getConfiguration() {
        return amc;
    }

    @Override
    public void destroy() throws Exception {
        stop();
    }

    public String getLocalHostname() {
        try {
            return InetAddress.getLocalHost().getHostName();
        } catch (UnknownHostException e) {
            try {
                return IOUtils.toString(Runtime.getRuntime().exec("hostname").getInputStream());
            } catch (IOException e1) {
                e1.printStackTrace();
                return "localhost";
            }
        }
    }
}