Package io.fabric8.gateway.fabric.haproxy

Source Code of io.fabric8.gateway.fabric.haproxy.FabricHaproxyGateway

/**
*  Copyright 2005-2014 Red Hat, Inc.
*
*  Red Hat 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 io.fabric8.gateway.fabric.haproxy;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import io.fabric8.api.Container;
import io.fabric8.api.FabricService;
import io.fabric8.api.Profile;
import io.fabric8.api.Profiles;
import io.fabric8.api.Version;
import io.fabric8.api.jcip.GuardedBy;
import io.fabric8.api.scr.AbstractComponent;
import io.fabric8.api.scr.Configurer;
import io.fabric8.utils.Closeables;
import io.fabric8.utils.Strings;
import io.fabric8.gateway.fabric.haproxy.model.BackEndServer;
import io.fabric8.gateway.fabric.haproxy.model.FrontEnd;
import io.fabric8.gateway.fabric.haproxy.model.OnValue;
import io.fabric8.gateway.handlers.http.HttpMappingRule;
import io.fabric8.gateway.handlers.http.MappedServices;
import io.fabric8.internal.Objects;

import org.apache.curator.framework.CuratorFramework;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.ConfigurationPolicy;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Modified;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.mvel2.ParserContext;
import org.mvel2.templates.CompiledTemplate;
import org.mvel2.templates.TemplateCompiler;
import org.mvel2.templates.TemplateRuntime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* An HTTP gateway which listens on a port and applies a number of {@link HttpMappingRuleConfiguration} instances to bind
* HTTP requests to different HTTP based services running within the fabric.
*/
@Service(FabricHaproxyGateway.class)
@Component(name = "io.fabric8.gateway.haproxy", immediate = true, metatype = true, policy = ConfigurationPolicy.REQUIRE,
        label = "Fabric8 HAProxy Gateway",
        description = "Automatically generates a haproxy configuration file to implement a reverse proxy from haproxy to any web services or web applications running inside the fabric")
public class FabricHaproxyGateway extends AbstractComponent {
    private static final transient Logger LOG = LoggerFactory.getLogger(FabricHaproxyGateway.class);
    private static final String TEMPLATE_FILE_NAME = "io.fabric8.gateway.haproxy.config.mvel";

    @Reference
    private Configurer configurer;
    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY, bind = "setFabricService", unbind = "unsetFabricService")
    private FabricService fabricService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY_MULTIPLE, bind = "setCurator", unbind = "unsetCurator")
    private CuratorFramework curator;

    @Property(name = "configFile",
            label = "Config file location", description = "The full file path of the generated configuration file created for haproxy to reuse")
    private String configFile;

    @Property(name = "reloadCommand",
            label = "Reload Haproxy Command", description = "The command line to execute when the haproxy configuration file has been regenerated so that haproxy can be safely restarted")
    private String reloadCommand;

    @Property(name = "reloadCommandDirectory",
            label = "Reload Command Directory", description = "The directory that should be used to run the reload command in")
    private String reloadCommandDirectory;

    private final ExecutorService executor = Executors.newSingleThreadExecutor();

    private Set<HttpMappingRule> mappingRuleConfigurations = new CopyOnWriteArraySet<HttpMappingRule>();
    private Runnable changeListener = new Runnable() {
        @Override
        public void run() {
            try {
                rewriteConfigurationFile();
                reloadHaproxy();
            } catch (Exception e) {
                LOG.warn("Failed to write haproxy config file: " + e, e);
            }
        }
    };

    private String templateText;
    @GuardedBy("this")
    private final ParserContext parserContext = new ParserContext();
    private CompiledTemplate template;

    public void rewriteConfigurationFile() throws IOException {
        LOG.info("Writing HAProxy file: " + configFile);
        File outFile = new File(configFile);
        outFile.getParentFile().mkdirs();
        PrintWriter writer = new PrintWriter(new FileWriter(outFile));
        try {
            Map<String, MappedServices> mappedServices = getMappedServices();

            CompiledTemplate compiledTemplate = getTemplate();
            Map<String, ?> data = createTemplateData();

            String renderedTemplate = TemplateRuntime.execute(compiledTemplate, parserContext, data).toString();
            writer.println(renderedTemplate);
        } finally {
            try {
                writer.close();
            } catch (Exception e) {
                LOG.debug("Caught while closing: " + e, e);
            }
        }
    }

    public void reloadHaproxy() throws Exception {
        if (Strings.isNotBlank(reloadCommand)) {
            LOG.info("Executing command: " + reloadCommand);
            Map<String, String> envVars = new HashMap<String, String>();
            envVars.putAll(System.getenv());

            // TODO set some other env vars?
            envVars.put("FABRIC8_HAPROXY_CONFIG", configFile);
            List<String> envVarList = new ArrayList<String>();
            for (Map.Entry<String, String> entry : envVars.entrySet()) {
                envVarList.add(entry.getKey() + "=" + entry.getValue());
            }
            final String[] envVarArray = envVarList.toArray(new String[envVarList.size()]);

            executor.execute(new Runnable() {

                @Override
                public void run() {
                    Process process = null;
                    Runtime runtime = Runtime.getRuntime();
                    try {
                        if (Strings.isNotBlank(reloadCommandDirectory)) {
                            File directory = new File(reloadCommandDirectory);
                            process = runtime.exec(reloadCommand, envVarArray, directory);
                        } else {
                            process = runtime.exec(reloadCommand, envVarArray);
                        }

                        forEachLine(process.getInputStream(), "stdout", new OnValue<String>() {
                            @Override
                            public void onValue(String value) {
                                LOG.info(value);
                            }
                        });

                        forEachLine(process.getErrorStream(), "stderr", new OnValue<String>() {
                            @Override
                            public void onValue(String value) {
                                LOG.error(value);
                            }
                        });
                        try {
                            int exitCode = process.waitFor();
                            LOG.info("command exit code: " + exitCode);
                        } catch (InterruptedException e) {
                            LOG.warn("Failed to wait for process exit code: " + e, e);
                        }
                    } catch (Exception e) {
                        LOG.error("Failed to create process: " + reloadCommand + ". " + e, e);
                    } finally {
                        if (process != null) {
                            try {
                                process.destroy();
                            } catch (Exception e) {
                                LOG.warn("Failed to destroy the process: " + e, e);
                            }
                        }
                    }
                }
            });
        }
    }

    public static void forEachLine(InputStream inputStream, String nameOfStream, OnValue<String> lineCallback) {
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        try {
            while (true) {
                String line = reader.readLine();
                if (line == null) break;
                lineCallback.onValue(line);
            }
        } catch (Exception e) {
            LOG.error("Failed to process " +
                    nameOfStream +
                    ": " + e, e);
        } finally {
            Closeables.closeQuietly(reader);
        }
    }

    protected Map<String, ?> createTemplateData() {
        Map<String, List<FrontEnd>> answer = new HashMap<String, List<FrontEnd>>();
        List<FrontEnd> frontEnds = new ArrayList<FrontEnd>();
        Set<Map.Entry<String, MappedServices>> entries = getMappedServices().entrySet();
        for (Map.Entry<String, MappedServices> entry : entries) {
            String uri = entry.getKey();
            MappedServices services = entry.getValue();
            String id = "b" + uri.replace('/', '_').replace('-', '_');
            while (id.endsWith("_")) {
                id = id.substring(0, id.length() - 1);
            }

            List<BackEndServer> backends = new ArrayList<BackEndServer>();
            FrontEnd frontEnd = new FrontEnd(id, uri, services, backends);
            frontEnds.add(frontEnd);
            Collection<String> serviceUrls = services.getServiceUrls();
            for (String serviceUrl : serviceUrls) {
                URL url = null;
                try {
                    url = new URL(serviceUrl);
                } catch (MalformedURLException e) {
                    LOG.warn("Ignore bad URL: " + e);
                }
                if (url != null) {
                    backends.add(new BackEndServer(url));
                }
            }
        }
        answer.put("frontEnds", frontEnds);
        return answer;
    }


    protected CompiledTemplate getTemplate() {
        String oldTemplateText = templateText;

        // lets lazy load template text from the fabric so we can configure it
        // explicitly to make testing outside of fabric easier
        if (templateText == null && fabricService != null) {
            Profile overlayProfile = fabricService.getCurrentContainer().getOverlayProfile();
            Profile effectiveProfile = Profiles.getEffectiveProfile(fabricService, overlayProfile);
            byte[] bytes = effectiveProfile.getFileConfiguration(TEMPLATE_FILE_NAME);
            if (bytes != null) {
                templateText = new String(bytes);
            }
        }
        Objects.notNull(templateText, "Could not find template text in profile config file: " + TEMPLATE_FILE_NAME);

        if (template == null || oldTemplateText == null || !oldTemplateText.equals(templateText)) {
            template = TemplateCompiler.compileTemplate(templateText, parserContext);
        }
        return template;
    }

    @Activate
    void activate(Map<String, ?> configuration) throws Exception {
        updateConfiguration(configuration);
        activateComponent();
    }


    @Modified
    void modified(Map<String, ?> configuration) throws Exception {
        deactivateInternal();
        updateConfiguration(configuration);
    }


    @Deactivate
    void deactivate() {
        deactivateInternal();
        deactivateComponent();
    }

    protected void updateConfiguration(Map<String, ?> configuration) throws Exception {
        configurer.configure(configuration, this);
    }

    protected void deactivateInternal() {
    }


    public void addMappingRuleConfiguration(HttpMappingRule mappingRuleConfiguration) {
        mappingRuleConfiguration.addChangeListener(changeListener);
        mappingRuleConfigurations.add(mappingRuleConfiguration);
    }


    public void removeMappingRuleConfiguration(HttpMappingRule mappingRuleConfiguration) {
        mappingRuleConfigurations.remove(mappingRuleConfiguration);
    }

    public Map<String, MappedServices> getMappedServices() {
        Map<String, MappedServices> answer = new HashMap<String, MappedServices>();
        for (HttpMappingRule mappingRuleConfiguration : mappingRuleConfigurations) {
            mappingRuleConfiguration.appendMappedServices(answer);
        }
        return answer;
    }

    // Properties
    //-------------------------------------------------------------------------

    public CuratorFramework getCurator() {
        return curator;
    }

    public void setCurator(CuratorFramework curator) {
        this.curator = curator;
    }

    public void unsetCurator(CuratorFramework curator) {
        this.curator = null;
    }

    public FabricService getFabricService() {
        return fabricService;
    }

    public void setFabricService(FabricService fabricService) {
        this.fabricService = fabricService;
    }

    public void unsetFabricService(FabricService fabricService) {
        this.fabricService = null;
    }

    public String getConfigFile() {
        return configFile;
    }

    public void setConfigFile(String configFile) {
        this.configFile = configFile;
    }

    public String getReloadCommand() {
        return reloadCommand;
    }

    public void setReloadCommand(String reloadCommand) {
        this.reloadCommand = reloadCommand;
    }

    public String getReloadCommandDirectory() {
        return reloadCommandDirectory;
    }

    public void setReloadCommandDirectory(String reloadCommandDirectory) {
        this.reloadCommandDirectory = reloadCommandDirectory;
    }

    /**
     * The source of the mvel template which is usually lazily
     * fetched from the fabric profile; though can be set explicitly
     * when testing this class outside of a fabric
     */
    public String getTemplateText() {
        return templateText;
    }

    public void setTemplateText(String templateText) {
        this.templateText = templateText;
    }

    /**
     * Returns the default profile version used to filter out the current versions of services
     * if no version expression is used the URI template
     */
    public String getGatewayVersion() {
        FabricService fabricService = getFabricService();
        if (fabricService != null) {
            Container currentContainer = fabricService.getCurrentContainer();
            if (currentContainer != null) {
                Version version = currentContainer.getVersion();
                if (version != null) {
                    return version.getId();
                }
            }
        }
        return null;
    }
}
TOP

Related Classes of io.fabric8.gateway.fabric.haproxy.FabricHaproxyGateway

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.