/** * 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.hadoop.gateway.services.topology.impl; import org.apache.commons.digester3.Digester; import org.apache.commons.digester3.binder.DigesterLoader; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.monitor.FileAlterationListener; import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; import org.apache.commons.io.monitor.FileAlterationMonitor; import org.apache.commons.io.monitor.FileAlterationObserver; import org.apache.hadoop.gateway.GatewayMessages; import org.apache.hadoop.gateway.audit.api.Action; import org.apache.hadoop.gateway.audit.api.ActionOutcome; import org.apache.hadoop.gateway.audit.api.AuditServiceFactory; import org.apache.hadoop.gateway.audit.api.Auditor; import org.apache.hadoop.gateway.audit.api.ResourceType; import org.apache.hadoop.gateway.audit.log4j.audit.AuditConstants; import org.apache.hadoop.gateway.config.GatewayConfig; import org.apache.hadoop.gateway.i18n.messages.MessagesFactory; import org.apache.hadoop.gateway.service.definition.ServiceDefinition; import org.apache.hadoop.gateway.services.ServiceLifecycleException; import org.apache.hadoop.gateway.services.topology.TopologyService; import org.apache.hadoop.gateway.topology.Topology; import org.apache.hadoop.gateway.topology.TopologyEvent; import org.apache.hadoop.gateway.topology.TopologyListener; import org.apache.hadoop.gateway.topology.TopologyMonitor; import org.apache.hadoop.gateway.topology.TopologyProvider; import org.apache.hadoop.gateway.topology.builder.TopologyBuilder; import org.apache.hadoop.gateway.topology.validation.TopologyValidator; import org.apache.hadoop.gateway.topology.xml.AmbariFormatXmlTopologyRules; import org.apache.hadoop.gateway.topology.xml.KnoxFormatXmlTopologyRules; import org.apache.hadoop.gateway.util.ServiceDefinitionsLoader; import org.eclipse.persistence.jaxb.JAXBContextProperties; import org.xml.sax.SAXException; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import static org.apache.commons.digester3.binder.DigesterLoader.newLoader; public class DefaultTopologyService extends FileAlterationListenerAdaptor implements TopologyService, TopologyMonitor, TopologyProvider, FileFilter, FileAlterationListener { private static Auditor auditor = AuditServiceFactory.getAuditService().getAuditor( AuditConstants.DEFAULT_AUDITOR_NAME, AuditConstants.KNOX_SERVICE_NAME, AuditConstants.KNOX_COMPONENT_NAME); private static final List<String> SUPPORTED_TOPOLOGY_FILE_EXTENSIONS = new ArrayList<String>(); static { SUPPORTED_TOPOLOGY_FILE_EXTENSIONS.add("xml"); SUPPORTED_TOPOLOGY_FILE_EXTENSIONS.add("conf"); } private static GatewayMessages log = MessagesFactory.get(GatewayMessages.class); private static DigesterLoader digesterLoader = newLoader(new KnoxFormatXmlTopologyRules(), new AmbariFormatXmlTopologyRules()); private FileAlterationMonitor monitor; private File directory; private Set<TopologyListener> listeners; private volatile Map<File, Topology> topologies; private Topology loadTopology(File file) throws IOException, SAXException, URISyntaxException, InterruptedException { final long TIMEOUT = 250; //ms final long DELAY = 50; //ms log.loadingTopologyFile(file.getAbsolutePath()); Topology topology; long start = System.currentTimeMillis(); while (true) { try { topology = loadTopologyAttempt(file); break; } catch (IOException e) { if (System.currentTimeMillis() - start < TIMEOUT) { log.failedToLoadTopologyRetrying(file.getAbsolutePath(), Long.toString(DELAY), e); Thread.sleep(DELAY); } else { throw e; } } catch (SAXException e) { if (System.currentTimeMillis() - start < TIMEOUT) { log.failedToLoadTopologyRetrying(file.getAbsolutePath(), Long.toString(DELAY), e); Thread.sleep(DELAY); } else { throw e; } } } return topology; } private Topology loadTopologyAttempt(File file) throws IOException, SAXException, URISyntaxException { Topology topology; Digester digester = digesterLoader.newDigester(); TopologyBuilder topologyBuilder = digester.parse(FileUtils.openInputStream(file)); if (null == topologyBuilder) { return null; } topology = topologyBuilder.build(); topology.setUri(file.toURI()); topology.setName(FilenameUtils.removeExtension(file.getName())); topology.setTimestamp(file.lastModified()); return topology; } private void redeployTopology(Topology topology) { File topologyFile = new File(topology.getUri()); try { TopologyValidator tv = new TopologyValidator(topology); if(tv.validateTopology()) { throw new SAXException(tv.getErrorString()); } long start = System.currentTimeMillis(); long limit = 1000L; // One second. long elapsed = 1; while (elapsed <= limit) { try { long origTimestamp = topologyFile.lastModified(); long setTimestamp = Math.max(System.currentTimeMillis(), topologyFile.lastModified() + elapsed); if(topologyFile.setLastModified(setTimestamp)) { long newTimstamp = topologyFile.lastModified(); if(newTimstamp > origTimestamp) { break; } else { Thread.sleep(10); elapsed = System.currentTimeMillis() - start; continue; } } else { auditor.audit(Action.REDEPLOY, topology.getName(), ResourceType.TOPOLOGY, ActionOutcome.FAILURE); log.failedToRedeployTopology(topology.getName()); break; } } catch (InterruptedException e) { auditor.audit(Action.REDEPLOY, topology.getName(), ResourceType.TOPOLOGY, ActionOutcome.FAILURE); log.failedToRedeployTopology(topology.getName(), e); e.printStackTrace(); } } } catch (SAXException e) { auditor.audit(Action.REDEPLOY, topology.getName(), ResourceType.TOPOLOGY, ActionOutcome.FAILURE); log.failedToRedeployTopology(topology.getName(), e); } } private List<TopologyEvent> createChangeEvents( Map<File, Topology> oldTopologies, Map<File, Topology> newTopologies) { ArrayList<TopologyEvent> events = new ArrayList<TopologyEvent>(); // Go through the old topologies and find anything that was deleted. for (File file : oldTopologies.keySet()) { if (!newTopologies.containsKey(file)) { events.add(new TopologyEvent(TopologyEvent.Type.DELETED, oldTopologies.get(file))); } } // Go through the new topologies and figure out what was updated vs added. for (File file : newTopologies.keySet()) { if (oldTopologies.containsKey(file)) { Topology oldTopology = oldTopologies.get(file); Topology newTopology = newTopologies.get(file); if (newTopology.getTimestamp() > oldTopology.getTimestamp()) { events.add(new TopologyEvent(TopologyEvent.Type.UPDATED, newTopologies.get(file))); } } else { events.add(new TopologyEvent(TopologyEvent.Type.CREATED, newTopologies.get(file))); } } return events; } private File calculateAbsoluteTopologiesDir(GatewayConfig config) { File topoDir = new File(config.getGatewayTopologyDir()); topoDir = topoDir.getAbsoluteFile(); return topoDir; } private void initListener(FileAlterationMonitor monitor, File directory) { this.directory = directory; this.monitor = monitor; FileAlterationObserver observer = new FileAlterationObserver(this.directory, this); observer.addListener(this); monitor.addObserver(observer); this.listeners = new HashSet<TopologyListener>(); this.topologies = new HashMap<File, Topology>(); //loadTopologies( this.directory ); } private void initListener(File directory) throws IOException, SAXException { // Increasing the monitoring interval to 5 seconds as profiling has shown // this is rather expensive in terms of generated garbage objects. initListener(new FileAlterationMonitor(5000L), directory); } private Map<File, Topology> loadTopologies(File directory) { Map<File, Topology> map = new HashMap<File, Topology>(); if (directory.isDirectory() && directory.canRead()) { for (File file : directory.listFiles(this)) { try { Topology loadTopology = loadTopology(file); if (null != loadTopology) { map.put(file, loadTopology); } else { auditor.audit(Action.LOAD, file.getAbsolutePath(), ResourceType.TOPOLOGY, ActionOutcome.FAILURE); log.failedToLoadTopology(file.getAbsolutePath()); } } catch (IOException e) { // Maybe it makes sense to throw exception auditor.audit(Action.LOAD, file.getAbsolutePath(), ResourceType.TOPOLOGY, ActionOutcome.FAILURE); log.failedToLoadTopology(file.getAbsolutePath(), e); } catch (SAXException e) { // Maybe it makes sense to throw exception auditor.audit(Action.LOAD, file.getAbsolutePath(), ResourceType.TOPOLOGY, ActionOutcome.FAILURE); log.failedToLoadTopology(file.getAbsolutePath(), e); } catch (Exception e) { // Maybe it makes sense to throw exception auditor.audit(Action.LOAD, file.getAbsolutePath(), ResourceType.TOPOLOGY, ActionOutcome.FAILURE); log.failedToLoadTopology(file.getAbsolutePath(), e); } } } return map; } public void deployTopology(Topology t){ try { File temp = new File(directory.getAbsolutePath() + "/" + t.getName() + ".xml.temp"); Package topologyPkg = Topology.class.getPackage(); String pkgName = topologyPkg.getName(); String bindingFile = pkgName.replace(".", "/") + "/topology_binding-xml.xml"; Map<String, Object> properties = new HashMap<String, Object>(1); properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, bindingFile); JAXBContext jc = JAXBContext.newInstance(pkgName, Topology.class.getClassLoader(), properties); Marshaller mr = jc.createMarshaller(); mr.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); mr.marshal(t, temp); File topology = new File(directory.getAbsolutePath() + "/" + t.getName() + ".xml"); if(!temp.renameTo(topology)) { FileUtils.forceDelete(temp); throw new IOException("Could not rename temp file"); } // This code will check if the topology is valid, and retrieve the errors if it is not. TopologyValidator validator = new TopologyValidator( topology.getAbsolutePath() ); if( !validator.validateTopology() ){ throw new SAXException( validator.getErrorString() ); } } catch (JAXBException e) { auditor.audit(Action.DEPLOY, t.getName(), ResourceType.TOPOLOGY, ActionOutcome.FAILURE); log.failedToDeployTopology(t.getName(), e); } catch (IOException io) { auditor.audit(Action.DEPLOY, t.getName(), ResourceType.TOPOLOGY, ActionOutcome.FAILURE); log.failedToDeployTopology(t.getName(), io); } catch (SAXException sx){ auditor.audit(Action.DEPLOY, t.getName(), ResourceType.TOPOLOGY, ActionOutcome.FAILURE); log.failedToDeployTopology(t.getName(), sx); } reloadTopologies(); } public void redeployTopologies(String topologyName) { for (Topology topology : getTopologies()) { if (topologyName == null || topologyName.equals(topology.getName())) { redeployTopology(topology); } } } public void reloadTopologies() { try { synchronized (this) { Map<File, Topology> oldTopologies = topologies; Map<File, Topology> newTopologies = loadTopologies(directory); List<TopologyEvent> events = createChangeEvents(oldTopologies, newTopologies); topologies = newTopologies; notifyChangeListeners(events); } } catch (Exception e) { // Maybe it makes sense to throw exception log.failedToReloadTopologies(e); } } public void deleteTopology(Topology t) { File topoDir = directory; if(topoDir.isDirectory() && topoDir.canRead()) { File[] results = topoDir.listFiles(); for (File f : results) { String fName = FilenameUtils.getBaseName(f.getName()); if(fName.equals(t.getName())) { f.delete(); } } } reloadTopologies(); } private void notifyChangeListeners(List<TopologyEvent> events) { for (TopologyListener listener : listeners) { try { listener.handleTopologyEvent(events); } catch (RuntimeException e) { auditor.audit(Action.LOAD, "Topology_Event", ResourceType.TOPOLOGY, ActionOutcome.FAILURE); log.failedToHandleTopologyEvents(e); } } } public Map<String, List<String>> getServiceTestURLs(Topology t, GatewayConfig config) { File tFile = null; Map<String, List<String>> urls = new HashMap<>(); if(directory.isDirectory() && directory.canRead()) { for(File f : directory.listFiles()){ if(FilenameUtils.removeExtension(f.getName()).equals(t.getName())){ tFile = f; } } } Set<ServiceDefinition> defs; if(tFile != null) { defs = ServiceDefinitionsLoader.getServiceDefinitions(new File(config.getGatewayServicesDir())); for(ServiceDefinition def : defs) { urls.put(def.getRole(), def.getTestURLs()); } } return urls; } public Collection<Topology> getTopologies() { Map<File, Topology> map = topologies; return Collections.unmodifiableCollection(map.values()); } @Override public void addTopologyChangeListener(TopologyListener listener) { listeners.add(listener); } @Override public void startMonitor() throws Exception { monitor.start(); } @Override public void stopMonitor() throws Exception { monitor.stop(); } @Override public boolean accept(File file) { boolean accept = false; if (!file.isDirectory() && file.canRead()) { String extension = FilenameUtils.getExtension(file.getName()); if (SUPPORTED_TOPOLOGY_FILE_EXTENSIONS.contains(extension)) { accept = true; } } return accept; } @Override public void onFileCreate(File file) { onFileChange(file); } @Override public void onFileDelete(java.io.File file) { onFileChange(file); } @Override public void onFileChange(File file) { reloadTopologies(); } @Override public void stop() { } @Override public void start() { } @Override public void init(GatewayConfig config, Map<String, String> options) throws ServiceLifecycleException { try { initListener(calculateAbsoluteTopologiesDir(config)); } catch (IOException io) { throw new ServiceLifecycleException(io.getMessage()); } catch (SAXException sax) { throw new ServiceLifecycleException(sax.getMessage()); } } }