Package io.fabric8.runtime.agent

Source Code of io.fabric8.runtime.agent.FabricAgent

/**
*  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.runtime.agent;

import io.fabric8.agent.download.DownloadManager;
import io.fabric8.agent.download.DownloadManagers;
import io.fabric8.agent.utils.AgentUtils;
import io.fabric8.api.Container;
import io.fabric8.api.FabricService;
import io.fabric8.api.Profile;
import io.fabric8.api.Profiles;
import io.fabric8.api.scr.AbstractComponent;
import io.fabric8.api.scr.ValidatingReference;
import io.fabric8.utils.Closeables;
import io.fabric8.utils.Strings;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.jar.Attributes;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.management.MBeanServer;
import javax.management.ObjectName;

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.Reference;
import org.apache.karaf.features.Feature;
import org.jboss.gravia.container.tomcat.WebAppContextListener;
import org.jboss.gravia.provision.Provisioner;
import org.jboss.gravia.provision.ResourceHandle;
import org.jboss.gravia.provision.ResourceInstaller;
import org.jboss.gravia.provision.spi.DefaultInstallerContext;
import org.jboss.gravia.resource.Capability;
import org.jboss.gravia.resource.ContentNamespace;
import org.jboss.gravia.resource.DefaultResourceBuilder;
import org.jboss.gravia.resource.IdentityNamespace;
import org.jboss.gravia.resource.ManifestBuilder;
import org.jboss.gravia.resource.MavenCoordinates;
import org.jboss.gravia.resource.Requirement;
import org.jboss.gravia.resource.Resource;
import org.jboss.gravia.resource.ResourceIdentity;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.Asset;
import org.jboss.shrinkwrap.api.exporter.ZipExporter;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.shrinkwrap.impl.base.asset.ZipFileEntryAsset;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(name = "io.fabric8.runtime.agent.FabricAgent", label = "Fabric8 Runtime Agent", immediate = true, policy = ConfigurationPolicy.IGNORE, metatype = false)
public class FabricAgent extends AbstractComponent implements FabricAgentMXBean {
    private static final Logger LOGGER = LoggerFactory.getLogger(FabricAgent.class);

    private static final String SERVICE_COMPONENT = "Service-Component";
    private static final String GENRATED_RESOURCE_IDENTITY = "io.fabric8.generated.fabric-profile";
    private static final String GENRATED_MAVEN_COORDS = "mvn:io.fabric8.generated/fabric-profile/1.0.0/war";

    private static final Pattern VALID_COMPONENT_PATH_PATTERN = Pattern.compile("[_a-zA-Z0-9\\-\\./]+");

    @Reference(referenceInterface = MBeanServer.class)
    private final ValidatingReference<MBeanServer> mbeanServer = new ValidatingReference<MBeanServer>();

    @Reference(referenceInterface = Provisioner.class)
    private final ValidatingReference<Provisioner> provisioner = new ValidatingReference<Provisioner>();
    @Reference(referenceInterface = FabricService.class)
    private final ValidatingReference<FabricService> fabricService = new ValidatingReference<FabricService>();
    private final Runnable onConfigurationChange = new Runnable() {
        @Override
        public void run() {
            submitUpdateJob();
        }
    };

    private final ExecutorService executor = Executors.newSingleThreadExecutor(new NamedThreadFactory("fabric8-agent"));
    private final ExecutorService downloadExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("fabric8-agent-downloader"));
    private ObjectName objectName;
    private Map<ResourceIdentity, ResourceHandle> resourcehandleMap = new ConcurrentHashMap<ResourceIdentity, ResourceHandle>();

    private ComponentContext componentContext;

    @Activate
    void activate(ComponentContext componentContext) {
        this.componentContext = componentContext;
        LOGGER.info("Activating");
        fabricService.get().trackConfiguration(onConfigurationChange);
        activateComponent();
        submitUpdateJob();
        try {
            MBeanServer anMBeanServer = mbeanServer.get();
            if (anMBeanServer != null) {
                if (objectName == null) {
                    objectName = new ObjectName("io.fabric8:type=RuntimeAgent");
                }
                anMBeanServer.registerMBean(this, objectName);
            } else {
                LOGGER.warn("No MBeanServer");
            }
        } catch (Exception e) {
            LOGGER.warn("Failed to register MBean " + objectName + ": " + e, e);
        }
    }

    @Deactivate
    void deactivate() {
        if (objectName != null) {
            try {
                MBeanServer anMBeanServer = mbeanServer.get();
                if (anMBeanServer != null) {
                    anMBeanServer.unregisterMBean(objectName);
                }
            } catch (Exception e) {
                LOGGER.warn("Failed to unregister MBean " + objectName + ": " + e, e);
            }
        }
        deactivateComponent();
        fabricService.get().untrackConfiguration(onConfigurationChange);
        executor.shutdown();
        try {
            executor.awaitTermination(1, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            // Ignore
        }
        executor.shutdownNow();
    }

    private void submitUpdateJob() {
        executor.submit(new Runnable() {
            @Override
            public void run() {
                if (isValid()) {
                    try {
                        updateInternal();
                    } catch (Exception e) {
                        LOGGER.warn("Caught:" + e, e);
                    }
                }
            }
        });
    }

    protected synchronized void updateInternal() {
        Profile effectiveProfile;
        try {
            Profile overlayProfile = fabricService.get().getCurrentContainer().getOverlayProfile();
            effectiveProfile = Profiles.getEffectiveProfile(fabricService.get(), overlayProfile);
        } catch (Exception ex) {
            LOGGER.warn("Failed to read container profile. This exception will be ignored..", ex);
            return;
        }

        List<String> resources = null;
        try {
            Map<String, File> artifacts = downloadProfileArtifacts(fabricService.get(), effectiveProfile);
            populateExplodedWar(artifacts);
            resources = updateProvisioning(artifacts, provisioner.get());
            updateStatus(Container.PROVISION_SUCCESS, null, resources);
        } catch (Throwable e) {
            if (isValid()) {
                LOGGER.warn("Exception updating provisioning: " + e, e);
                updateStatus(Container.PROVISION_ERROR, e, resources);
            } else {
                LOGGER.debug("Exception updating provisioning: " + e, e);
            }
        }
    }

    protected void updateStatus(String status, Throwable result, List<String> resources) {
        try {
            FabricService fs = fabricService.get();

            if (fs != null) {
                Container container = fs.getCurrentContainer();
                String e;
                if (result == null) {
                    e = null;
                } else {
                    StringWriter sw = new StringWriter();
                    result.printStackTrace(new PrintWriter(sw));
                    e = sw.toString();
                }
                if (resources != null) {
                    container.setProvisionList(resources);
                }
                container.setProvisionResult(status);
                container.setProvisionException(e);
            } else {
                LOGGER.info("FabricService not available");
            }
        } catch (Throwable e) {
            LOGGER.warn("Unable to set provisioning result");
        }
    }

    protected Map<String, File> downloadProfileArtifacts(FabricService fabric, Profile profile) throws Exception {
        updateStatus(Container.PROVISION_DOWNLOADING, null, null);
        Set<String> bundles = new LinkedHashSet<String>();
        Set<Feature> features = new LinkedHashSet<Feature>();
        bundles.addAll(profile.getBundles());

        DownloadManager downloadManager = DownloadManagers.createDownloadManager(fabric, downloadExecutor);
        AgentUtils.addFeatures(features, fabric, downloadManager, profile);
        //return AgentUtils.downloadProfileArtifacts(fabricService.get(), downloadManager, profile);
        return AgentUtils.downloadBundles(downloadManager, features, bundles,
                Collections.<String>emptySet());
    }

    protected void populateExplodedWar(Map<String, File> artifacts) throws IOException {
        updateStatus("populating profile war", null, null);
        ClassLoader original = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(FabricAgent.class.getClassLoader());
            final WebArchive archive = ShrinkWrap.create(WebArchive.class, "profile.war");
            final Set<String> components = new HashSet<String>();
            for (Map.Entry<String, File> entry : artifacts.entrySet()) {
                String name = entry.getKey();
                File f = entry.getValue();
                if (!isWar(name, f)) {
                    archive.addAsLibrary(f);
                    Manifest mf = readManifest(f);
                    if (mf.getMainAttributes().containsKey(new Attributes.Name(SERVICE_COMPONENT))) {
                        String serviceComponents = mf.getMainAttributes().getValue(SERVICE_COMPONENT);
                        for (String component : Strings.splitAndTrimAsList(serviceComponents, ",")) {
                            if (VALID_COMPONENT_PATH_PATTERN.matcher(component).matches()) {
                                archive.add(new ZipFileEntryAsset(new ZipFile(f, ZipFile.OPEN_READ), new ZipEntry(component)), component);
                                components.add(component);
                            }
                        }
                    }
                }
            }

            archive.addClass(WebAppContextListener.class);
            archive.addAsWebInfResource("web.xml");
            archive.addAsWebResource("context.xml", "META-INF/context.xml");
            archive.setManifest(new Asset() {
                @Override
                public InputStream openStream() {
                    return new ManifestBuilder()
                            .addIdentityCapability(GENRATED_RESOURCE_IDENTITY, "1.0.0")
                            .addManifestHeader(SERVICE_COMPONENT, Strings.join(components, ",")).openStream();
                }
            });

            File profileWar = componentContext.getBundleContext().getDataFile("fabric-profile.war");
            archive.as(ZipExporter.class).exportTo(profileWar, true);
            artifacts.put(GENRATED_MAVEN_COORDS, profileWar);
        } finally {
            Thread.currentThread().setContextClassLoader(original);
        }
        updateStatus("populated profile war", null, null);
    }

    private static Manifest readManifest(File file) throws IOException {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            JarInputStream jis = new JarInputStream(fis);
            return jis.getManifest();
        } finally {
            Closeables.closeQuietly(fis);
        }
    }

    protected List<String> updateProvisioning(Map<String, File> artifacts, Provisioner provisionService) throws Exception {
        ResourceInstaller resourceInstaller = provisionService.getResourceInstaller();
        Map<ResourceIdentity, Resource> installedResources = getInstalledResources(provisionService);
        Map<Requirement, Resource> requirements = new HashMap<Requirement, Resource>();
        Set<Map.Entry<String, File>> entries = artifacts.entrySet();
        List<Resource> resourcesToInstall = new ArrayList<Resource>();
        List<String> resourceUrisInstalled = new ArrayList<String>();
        updateStatus(Container.PROVISION_INSTALLING, null, null);
        for (Map.Entry<String, File> entry : entries) {
            String name = entry.getKey();
            File file = entry.getValue();

            String coords = name;
            int idx = coords.lastIndexOf(':');
            if (idx > 0) {
                coords = name.substring(idx + 1);
            }
            // lets switch to gravia's mvn coordinates
            coords = coords.replace('/', ':');
            MavenCoordinates mvnCoords = parse(coords);
            URL url = file.toURI().toURL();
            if (url == null) {
                LOGGER.warn("Could not find URL for file " + file);
                continue;
            }

            // TODO lets just detect wars for now for servlet engines - how do we decide on WildFly?

            boolean isShared = !isWar(name, file);
            Resource resource = findMavenResource(mvnCoords, url, isShared);
            if (resource == null) {
                LOGGER.warn("Could not find resource for " + mvnCoords + " and " + url);
            } else {
                ResourceIdentity identity = resource.getIdentity();
                Resource oldResource = installedResources.remove(identity);
                if (oldResource == null && !resourcehandleMap.containsKey(identity)) {
                    if (isShared) {
                        // TODO lest not deploy shared stuff for now since bundles throw an exception when trying to stop them
                        // which breaks the tests ;)
                        LOGGER.debug("TODO not installing " + (isShared ? "shared" : "non-shared") + " resource: " + identity);
                    } else {
                        LOGGER.info("Installing " + (isShared ? "shared" : "non-shared") + " resource: " + identity);
                        resourcesToInstall.add(resource);
                        resourceUrisInstalled.add(name);
                    }
                }
            }
        }

        for (Resource installedResource : installedResources.values()) {
            ResourceIdentity identity = installedResource.getIdentity();
            ResourceHandle resourceHandle = resourcehandleMap.get(identity);
            if (resourceHandle == null) {
                // TODO should not really happen when we can ask about the installed Resources
                LOGGER.warn("TODO: Cannot uninstall " + installedResource + " as we have no handle!");
            } else {
                LOGGER.info("Uninstalling " + installedResource);
                resourceHandle.uninstall();
                resourcehandleMap.remove(identity);
                LOGGER.info("Uninstalled " + installedResource);
            }
        }
        if (resourcesToInstall.size() > 0) {
            LOGGER.info("Installing " + resourcesToInstall.size() + " resource(s)");
            Set<ResourceHandle> resourceHandles = new LinkedHashSet<>();
            ResourceInstaller.Context context = new DefaultInstallerContext(resourcesToInstall, requirements);
            for (Resource resource : resourcesToInstall) {
                resourceHandles.add(resourceInstaller.installResource(context, resource));
            }

            LOGGER.info("Got " + resourceHandles.size() + " resource handle(s)");

            for (ResourceHandle resourceHandle : resourceHandles) {
                resourcehandleMap.put(resourceHandle.getResource().getIdentity(), resourceHandle);
            }
        }
        return resourceUrisInstalled;
    }

    protected Map<ResourceIdentity, Resource> getInstalledResources(Provisioner provisionService) {
        Map<ResourceIdentity, Resource> installedResources = new HashMap<ResourceIdentity, Resource>();

        // lets add the handles we already know about
        for (Map.Entry<ResourceIdentity, ResourceHandle> entry : resourcehandleMap.entrySet()) {
            installedResources.put(entry.getKey(), entry.getValue().getResource());
        }
        try {
            Iterator<Resource> resources = provisionService.getEnvironment().getResources();
            while (resources.hasNext()) {
                Resource resource = resources.next();
                installedResources.put(resource.getIdentity(), resource);
            }
        } catch (Throwable e) {
            LOGGER.warn("Ignoring error finding current resources: " + e, e);
        }
        return installedResources;
    }


    public Resource findMavenResource(MavenCoordinates mavenid, URL contentURL, boolean isShared) {
        LOGGER.debug("Find maven providers for: {}", mavenid);
        Resource result = null;
        if (contentURL != null) {
            DefaultResourceBuilder builder = new DefaultResourceBuilder();
            Capability identCap = builder.addIdentityCapability(mavenid);
            Capability ccap = builder.addCapability(ContentNamespace.CONTENT_NAMESPACE, null, null);
            ccap.getAttributes().put(ContentNamespace.CAPABILITY_URL_ATTRIBUTE, contentURL);
            if (isShared) {
                identCap.getAttributes().put(IdentityNamespace.CAPABILITY_SHARED_ATTRIBUTE, "true");
            } else {
                // lets default to using just the artifact id for the context path
                identCap.getAttributes().put("contextPath", mavenid.getArtifactId());
            }
            LOGGER.debug("Found maven resource: {}", result = builder.getResource());
        }

        return result;
    }

    private static boolean isWar(String name, File file) {
        return name.startsWith("war:") || name.contains("/war/") ||
                file.getName().toLowerCase().endsWith(".war");
    }

    //TODO: This needs to be fixed at gravia
    private static MavenCoordinates parse(String coordinates) {
        MavenCoordinates result;
        String[] parts = coordinates.split(":");
        if (parts.length == 3) {
            result =  MavenCoordinates.create(parts[0], parts[1], parts[2], null, null);
        } else if (parts.length == 4) {
            result = MavenCoordinates.create(parts[0], parts[1], parts[2], parts[3], null);
        } else if (parts.length == 5) {
            result = MavenCoordinates.create(parts[0], parts[1], parts[2], parts[3], parts[4]);
        } else {
            throw new IllegalArgumentException("Invalid coordinates: " + coordinates);
        }
        return result;
    }

    void bindMbeanServer(MBeanServer service) {
        this.mbeanServer.bind(service);
    }

    void unbindMbeanServer(MBeanServer service) {
        this.mbeanServer.unbind(service);
    }

    void bindProvisioner(Provisioner service) {
        this.provisioner.bind(service);
    }

    void unbindProvisioner(Provisioner service) {
        this.provisioner.unbind(service);
    }

    void bindFabricService(FabricService fabricService) {
        this.fabricService.bind(fabricService);
    }

    void unbindFabricService(FabricService fabricService) {
        this.fabricService.unbind(fabricService);
    }


    private static class NamedThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        NamedThreadFactory(String prefix) {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                    Thread.currentThread().getThreadGroup();
            namePrefix = prefix + "-" +
                    poolNumber.getAndIncrement() +
                    "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                    namePrefix + threadNumber.getAndIncrement(),
                    0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }
}
TOP

Related Classes of io.fabric8.runtime.agent.FabricAgent

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.