/** * Copyright 2014-2017 Linagora, Université Joseph Fourier, Floralis * * The present code is developed in the scope of the joint LINAGORA - * Université Joseph Fourier - Floralis research program and is designated * as a "Result" pursuant to the terms and conditions of the LINAGORA * - Université Joseph Fourier - Floralis research program. Each copyright * holder of Results enumerated here above fully & independently holds complete * ownership of the complete Intellectual Property rights applicable to the whole * of said Results, and may freely exploit it in any manner which does not infringe * the moral rights of the other copyright holders. * * 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 net.roboconf.agent.internal.misc; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Map; import java.util.Properties; import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.codec.binary.Base64; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import net.roboconf.agent.internal.AgentProperties; import net.roboconf.core.Constants; import net.roboconf.core.utils.Utils; import net.roboconf.messaging.api.MessagingConstants; /** * @author Noël - LIG * @author Pierre-Yves Gibello - Linagora */ public final class UserDataUtils { public static final String CONF_FILE_AGENT = "net.roboconf.agent.configuration.cfg"; /** * Private empty constructor. */ private UserDataUtils() { // nothing } /** * Configures the agent from a IaaS registry. * @param logger a logger * @return the agent's data, or null if they could not be parsed */ public static AgentProperties findParametersForAmazonOrOpenStack( Logger logger ) { // Copy the user data String userData = ""; InputStream in = null; try { URL userDataUrl = new URL( "http://169.254.169.254/latest/user-data" ); in = userDataUrl.openStream(); ByteArrayOutputStream os = new ByteArrayOutputStream(); Utils.copyStreamSafely( in, os ); userData = os.toString( "UTF-8" ); } catch( IOException e ) { logger.severe( "The agent properties could not be read. " + e.getMessage()); Utils.logException( logger, e ); } AgentProperties result = null; in = null; try { // Parse the user data result = AgentProperties.readIaasProperties( userData, logger ); // We need to ask our IP address because we may have several network interfaces. URL userDataUrl = new URL( "http://169.254.169.254/latest/meta-data/public-ipv4" ); in = userDataUrl.openStream(); ByteArrayOutputStream os = new ByteArrayOutputStream(); Utils.copyStreamSafely( in, os ); String ip = os.toString( "UTF-8" ); if(! AgentUtils.isValidIP( ip )) { // Failed retrieving public IP: try private one instead Utils.closeQuietly( in ); userDataUrl = new URL( "http://169.254.169.254/latest/meta-data/local-ipv4" ); in = userDataUrl.openStream(); os = new ByteArrayOutputStream(); Utils.copyStreamSafely( in, os ); ip = os.toString( "UTF-8" ); } if( ! AgentUtils.isValidIP( ip )) throw new IOException("No IP address could be retrieved (either public-ipv4 or local-ipv4)"); result.setIpAddress( os.toString( "UTF-8" )); } catch( IOException e ) { logger.severe( "The network properties could not be read. " + e.getMessage()); Utils.logException( logger, e ); } finally { Utils.closeQuietly( in ); } return result; } /** * Configures the agent from Azure. * @param logger a logger * @return the agent's data, or null if they could not be parsed */ public static AgentProperties findParametersForAzure( Logger logger ) { String userData = ""; try { // Get the user data from /var/lib/waagent/ovf-env.xml and decode it String userDataEncoded = getValueOfTagInXMLFile( "/var/lib/waagent/ovf-env.xml", "CustomData" ); userData = new String( Base64.decodeBase64( userDataEncoded.getBytes( "UTF-8" )), "UTF-8" ); } catch( IOException | ParserConfigurationException | SAXException e ) { logger.severe( "The agent properties could not be read. " + e.getMessage()); Utils.logException( logger, e ); } // Get the public IP Address from /var/lib/waagent/SharedConfig.xml AgentProperties result = null; String publicIPAddress; try { result = AgentProperties.readIaasProperties( userData, logger ); publicIPAddress = getSpecificAttributeOfTagInXMLFile( "/var/lib/waagent/SharedConfig.xml", "Endpoint", "loadBalancedPublicAddress" ); result.setIpAddress( publicIPAddress ); } catch( ParserConfigurationException | SAXException e ) { logger.severe( "The agent could not retrieve a public IP address. " + e.getMessage()); Utils.logException( logger, e ); } catch( IOException e ) { logger.severe( "The agent could not retrieve its configuration. " + e.getMessage()); Utils.logException( logger, e ); } return result; } /** * Configures the agent from VMWare. * @param logger a logger * @return the agent's data, or null if they could not be parsed */ public static AgentProperties findParametersForVmware( Logger logger ) { File propertiesFile = new File("/tmp/roboconf.properties"); try { int retries = 30; while((! propertiesFile.exists() || ! propertiesFile.canRead()) && retries-- > 0) { logger.fine("Agent tries to read properties file " + propertiesFile + ": trial #" + (30-retries)); try { Thread.sleep( 2000 ); } catch( InterruptedException e ) { throw new IOException("Can't read properties file: " + e); } } AgentProperties result = AgentProperties.readIaasProperties(Utils.readPropertiesFile(propertiesFile)); /* * HACK for specific IaaS configurations (using properties file in a VMWare-like manner) * Try to pick IP address... in the case we are on OpenStack or any IaaS with amazon-compatible API * Some configurations (with floating IPs) do not provide network interfaces exposing public IPs ! */ InputStream in = null; try { URL userDataUrl = new URL( "http://169.254.169.254/latest/meta-data/public-ipv4" ); in = userDataUrl.openStream(); ByteArrayOutputStream os = new ByteArrayOutputStream(); Utils.copyStreamSafely( in, os ); String ip = os.toString( "UTF-8" ); if(! AgentUtils.isValidIP( ip )) { // Failed retrieving public IP: try private one instead Utils.closeQuietly( in ); userDataUrl = new URL( "http://169.254.169.254/latest/meta-data/local-ipv4" ); in = userDataUrl.openStream(); os = new ByteArrayOutputStream(); Utils.copyStreamSafely( in, os ); ip = os.toString( "UTF-8" ); } if(AgentUtils.isValidIP( ip )) result.setIpAddress( os.toString( "UTF-8" )); } catch( IOException e ) { Utils.logException( logger, e ); } finally { Utils.closeQuietly( in ); } /* HACK ends here (see comment above). Removing it is harmless on classical VMWare configurations. */ return result; } catch(IOException e) { logger.fine("Agent failed to read properties file " + propertiesFile); return null; } } /** * Reconfigures the messaging. * @param etcDir the KARAF_ETC directory * @param msgData the messaging configuration parameters */ public static void reconfigureMessaging( String etcDir, Map<String,String> msgData ) throws IOException { String messagingType = msgData.get( MessagingConstants.MESSAGING_TYPE_PROPERTY ); Logger.getLogger( UserDataUtils.class.getName()).fine( "Messaging type for reconfiguration: " + messagingType ); if( ! Utils.isEmptyOrWhitespaces( etcDir )) { // Write the messaging configuration File f = new File( etcDir, "net.roboconf.messaging." + messagingType + ".cfg" ); Logger logger = Logger.getLogger( UserDataUtils.class.getName()); Properties props = Utils.readPropertiesFileQuietly( f, logger ); props.putAll( msgData ); props.remove( MessagingConstants.MESSAGING_TYPE_PROPERTY ); Utils.writePropertiesFile( props, f ); // Set the messaging type f = new File( etcDir, CONF_FILE_AGENT ); props = Utils.readPropertiesFileQuietly( f, Logger.getLogger( UserDataUtils.class.getName())); props.put( Constants.MESSAGING_TYPE, messagingType ); Utils.writePropertiesFile( props, f ); } } // FIXME: there must be a shorter way with XPath... private static String getValueOfTagInXMLFile( String filePath, String tagName ) throws ParserConfigurationException, SAXException, IOException { File fXmlFile = new File(filePath); DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); Document doc = dBuilder.parse(fXmlFile); // Optional, but recommended // Read this: http://stackoverflow.com/questions/13786607/normalization-in-dom-parsing-with-java-how-does-it-work doc.getDocumentElement().normalize(); NodeList nList = doc.getElementsByTagName(tagName); String valueOfTagName = ""; for (int temp = 0; temp < nList.getLength(); temp++) { Node nNode = nList.item(temp); valueOfTagName = nNode.getTextContent(); } return valueOfTagName; } private static String getSpecificAttributeOfTagInXMLFile(String filePath, String tagName, String attrName) throws ParserConfigurationException, SAXException, IOException { File fXmlFile = new File(filePath); DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); Document doc = dBuilder.parse(fXmlFile); doc.getDocumentElement().normalize(); NodeList nList = doc.getElementsByTagName(tagName); Node aNode = nList.item(2); NamedNodeMap attributes = aNode.getAttributes(); String attrValue = ""; for (int a = 0; a < attributes.getLength(); a++) { Node theAttribute = attributes.item(a); if (attrName.equals(theAttribute.getNodeName())) attrValue = theAttribute.getTextContent().split(":")[0]; } return attrValue; } }