summaryrefslogblamecommitdiffstats
path: root/src/main/java/org/openslx/util/vm/VmwareMetaData.java
blob: 233765b8106a25b2929815c1b8ca1618dca91d56 (plain) (tree)



















                                                     
                                                 





                                                                                      
 
                                                                                             
 
                                                                                                                                      
 

                                                                   
 






                                                                                                                             




                                                                      

                                                                     

                                                                             
















                                                                                           
                                             






























                                                                                                   


                                                    









                                                                                       
                                                 










                                                                                              
                                                                                                                       











                                                                                                        







                                                                                  

         



                                                                                               


                                                          




                                                               










                                                                      


                                                       
                                                         
























































                                                                                                                        
                                                         




                                    

                                                                                                                         







                                                                         





















































                                                                                                                          


                 
                                                  
         
                                                                                   





                                           

         
                                          
                                                 
 











                                                               
                                        
         













































































                                                                                                    

         
 
package org.openslx.util.vm;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;
import org.openslx.bwlp.thrift.iface.OperatingSystem;
import org.openslx.bwlp.thrift.iface.Virtualizer;
import org.openslx.util.Util;

public class VmwareMetaData extends VmMetaData
{

	private static final Logger LOGGER = Logger.getLogger( VmwareMetaData.class );

	private static final Virtualizer virtualizer = new Virtualizer( "vmware", "VMware" );

	private static final Pattern hddKey = Pattern.compile( "^(ide\\d|scsi\\d|sata\\d):?(\\d)?\\.(.*)", Pattern.CASE_INSENSITIVE );

	// Lowercase list of allowed settings for upload (as regex)
	private static final Pattern[] whitelist;

	// Init static members
	static {
		String[] list = { "^guestos", "^uuid\\.bios", "^config\\.version", "^ehci\\.",
				"mks\\.", "virtualhw\\.", "^sound\\.", "\\.pcislotnumber$", "^pcibridge", "\\.virtualdev$" };
		whitelist = new Pattern[ list.length ];
		for ( int i = 0; i < list.length; ++i ) {
			whitelist[i] = Pattern.compile( list[i] );
		}
	}

	private final Map<String, Controller> disks = new HashMap<>();

	private final Map<String, String> slxLines = new HashMap<>();

	private final StringBuilder cleanedConfig = new StringBuilder( 200 );

	public VmwareMetaData( List<OperatingSystem> osList, File file ) throws IOException
	{
		super( osList );
		int todo = (int)Math.min( 100000, file.length() );
		int offset = 0;
		byte[] data = new byte[ todo ];
		FileInputStream fr = null;
		try {
			fr = new FileInputStream( file );
			while ( todo > 0 ) {
				int ret = fr.read( data, offset, todo );
				if ( ret <= 0 )
					break;
				todo -= ret;
				offset += ret;
			}
		} finally {
			Util.safeClose( fr );
		}
		init( data, offset );
	}

	public VmwareMetaData( List<OperatingSystem> osList, byte[] vmxContent, int length )
	{
		super( osList );
		init( vmxContent, length );
	}

	private void init( byte[] vmxContent, int length )
	{
		String csName = detectCharset( new ByteArrayInputStream( vmxContent, 0, length ) );
		Charset cs = null;
		try {
			cs = Charset.forName( csName );
		} catch ( Exception e ) {
			LOGGER.warn( "Could not instantiate charset " + csName, e );
		}
		if ( cs == null )
			cs = StandardCharsets.ISO_8859_1;
		try {
			loadVmx( new ByteArrayInputStream( vmxContent, 0, length ), cs );
		} catch ( IOException e ) {
			LOGGER.warn( "Exception when loading vmx from byte array (how!?)", e );
		}
	}

	private void loadVmx( InputStream is, Charset cs ) throws IOException
	{
		LOGGER.info( "Loading VMX with charset " + cs.name() );
		// Prepare filtered vmx
		addFiltered( ".encoding", "UTF-8" );
		// Read line-by-line
		BufferedReader reader = null;
		try {
			reader = new BufferedReader( new InputStreamReader( is, cs ) );
			String line;
			while ( ( line = reader.readLine() ) != null ) {
				KeyValuePair entry = parse( line );
				if ( entry != null )
					handleLoadEntry( entry );
			}
		} finally {
			Util.safeClose( reader );
		}
		// Now find the HDDs and add to list
		for ( Entry<String, Controller> cEntry : disks.entrySet() ) {
			Controller controller = cEntry.getValue();
			String controllerType = cEntry.getKey();
			if ( !controller.present )
				continue;
			for ( Entry<String, Device> dEntry : controller.devices.entrySet() ) {
				Device device = dEntry.getValue();
				if ( !device.present )
					continue; // Not present
				if ( device.deviceType != null && !device.deviceType.toLowerCase().endsWith( "disk" ) )
					continue; // Not a HDD
				DriveBusType bus = null;
				if ( controllerType.startsWith( "ide" ) ) {
					bus = DriveBusType.IDE;
				} else if ( controllerType.startsWith( "scsi" ) ) {
					bus = DriveBusType.SCSI;
				} else if ( controllerType.startsWith( "sata" ) ) {
					bus = DriveBusType.SATA;
				}
				hdds.add( new HardDisk( controller.virtualDev, bus, device.filename ) );
			}
		}
		// Add HDD to cleaned vmx
		if ( !hdds.isEmpty() ) {
			HardDisk hdd = hdds.get( 0 );
			addFiltered( "#SLX_HDD_BUS", hdd.bus.toString() );
			if ( hdd.chipsetDriver != null ) {
				addFiltered( "#SLX_HDD_CHIP", hdd.chipsetDriver );
			}
		}
	}

	private void addFiltered( String key, String value )
	{
		cleanedConfig.append( key ).append( " = \"" ).append( value ).append( "\"\n" );
	}

	private void handleLoadEntry( KeyValuePair entry )
	{
		// Check if it's one of our special keys
		if ( entry.key.startsWith( "#SLX_" ) ) {
			slxLines.put( entry.key, entry.value );
			return;
		}
		String lowerKey = entry.key.toLowerCase();
		// Cleaned vmx construction
		for ( Pattern exp : whitelist ) {
			if ( exp.matcher( lowerKey ).find() ) {
				addFiltered( entry.key, entry.value );
				break;
			}
		}
		//
		// Dig Usable meta data
		if ( lowerKey.equals( "guestos" ) ) {
			setOs( "vmware", entry.value );
			return;
		}
		if ( lowerKey.equals( "displayname" ) ) {
			displayName = entry.value;
			return;
		}
		Matcher hdd = hddKey.matcher( entry.key );
		if ( hdd.find() ) {
			handleHddEntry( hdd.group( 1 ).toLowerCase(), hdd.group( 2 ), hdd.group( 3 ), entry.value );
		}
	}

	private void handleHddEntry( String controllerStr, String deviceStr, String property, String value )
	{
		Controller controller = disks.get( controllerStr );
		if ( controller == null ) {
			controller = new Controller();
			disks.put( controllerStr, controller );
		}
		if ( deviceStr == null || deviceStr.isEmpty() ) {
			// Controller property
			if ( property.equalsIgnoreCase( "present" ) ) {
				controller.present = Boolean.parseBoolean( value );
			} else if ( property.equalsIgnoreCase( "virtualDev" ) ) {
				controller.virtualDev = value;
			}
			return;
		}
		// Device property
		Device device = controller.devices.get( deviceStr );
		if ( device == null ) {
			device = new Device();
			controller.devices.put( deviceStr, device );
		}
		if ( property.equalsIgnoreCase( "deviceType" ) ) {
			device.deviceType = value;
		} else if ( property.equalsIgnoreCase( "filename" ) ) {
			device.filename = value;
		} else if ( property.equalsIgnoreCase( "present" ) ) {
			device.present = Boolean.parseBoolean( value );
		}
	}

	private String detectCharset( InputStream is )
	{
		BufferedReader csDetectReader = null;
		try {
			csDetectReader = new BufferedReader( new InputStreamReader( is, StandardCharsets.ISO_8859_1 ) );
			String line;
			while ( ( line = csDetectReader.readLine() ) != null ) {
				KeyValuePair entry = parse( line );
				if ( entry == null )
					continue;
				if ( entry.key.equals( ".encoding" ) ) {
					return entry.value;
				}
			}
		} catch ( Exception e ) {
			LOGGER.warn( "Could not detect charset, fallback to latin1", e );
		} finally {
			Util.safeClose( csDetectReader );
		}
		// Dumb fallback
		return "ISO-8859-1";
	}

	private static final Pattern settingMatcher = Pattern.compile( "^\\s*(#?[a-z0-9\\.\\:_]+)\\s*=\\s*\"(.*)\"\\s*$",
			Pattern.CASE_INSENSITIVE );

	private KeyValuePair parse( String line )
	{
		Matcher matcher = settingMatcher.matcher( line );
		if ( !matcher.matches() )
			return null;
		return new KeyValuePair(
				matcher.group( 1 ), matcher.group( 2 ) );

	}

	public boolean addHddTemplate( String diskImagePath )
	{
		DriveBusType bus;
		try {
			bus = DriveBusType.valueOf( slxLines.get( "#SLX_HDD_BUS" ) );
		} catch ( Exception e ) {
			LOGGER.warn( "Unknown bus type: " + slxLines.get( "#SLX_HDD_BUS" ) + ". Cannot add hdd config." );
			return false;
		}
		String chipset = slxLines.get( "#SLX_HDD_CHIP" );
		switch ( bus ) {
		case IDE:
			addFiltered( "ide0.present", "TRUE" );
			addFiltered( "ide0:0.present", "TRUE" );
			addFiltered( "ide0:0.deviceType", "disk" );
			addFiltered( "ide0:0.fileName", diskImagePath );
			return true;
		case SATA:
			// Cannot happen, use lsisas1068
		case SCSI:
			addFiltered( "scsi0.present", "TRUE" );
			addFiltered( "scsi0:0.present", "TRUE" );
			addFiltered( "scsi0:0.deviceType", "disk" );
			addFiltered( "scsi0:0.fileName", diskImagePath );
			if ( chipset != null ) {
				addFiltered( "scsi0.virtualDev", chipset );
			}
			return true;
		default:
			LOGGER.warn( "Unknown HDD bus type: " + bus.toString() );
			return false;
		}
	}

	public boolean addEthernetNat()
	{
		addFiltered( "ethernet0.present", "TRUE" );
		addFiltered( "ethernet0.connectionType", "nat" );
		return true;
	}

	public boolean addDisplayName( String name )
	{
		addFiltered( "displayName", name );
		return true;
	}

	public void setOs( String vendorOsId )
	{
		addFiltered( "guestOS", vendorOsId );
		setOs( "vmware", vendorOsId );
	}

	@Override
	public byte[] getFilteredDefinitionArray()
	{
		return cleanedConfig.toString().getBytes( StandardCharsets.UTF_8 );
	}

	@Override
	public Virtualizer getVirtualizer()
	{
		return virtualizer;
	}

	// ###############################
	// TODO Structured approach to above mess

	private static class KeyValuePair
	{
		public final String key;
		public final String value;

		public KeyValuePair( String key, String value )
		{
			this.key = key;
			this.value = value;
		}
	}

	private static class ConfigEntry
	{
		private String value;
		private boolean forFiltered;
		private boolean forGenerated;

		public ConfigEntry( String value )
		{
			this.value = value;
		}

		public ConfigEntry filtered( boolean set )
		{
			this.forFiltered = set;
			return this;
		}

		public ConfigEntry generated( boolean set )
		{
			this.forGenerated = set;
			return this;
		}

	}

	private static class ConfigEntries
	{
		private Map<String, ConfigEntry> entries = new HashMap<>();

		public ConfigEntry set( String key, String value )
		{
			return entries.put( key, new ConfigEntry( value ) );
		}

		public ConfigEntry set( KeyValuePair entry )
		{
			return set( entry.key, entry.value );
		}

		public String get( boolean filteredRequired, boolean generatedRequired )
		{
			StringBuilder sb = new StringBuilder( 300 );
			for ( Entry<String, ConfigEntry> entry : entries.entrySet() ) {
				ConfigEntry value = entry.getValue();
				if ( ( !filteredRequired || value.forFiltered ) &&
						( !generatedRequired || value.forGenerated ) ) {
					sb.append( entry.getKey() );
					sb.append( " = \"" );
					sb.append( entry.getValue() );
					sb.append( "\"\n" );
				}
			}
			return sb.toString();
		}
	}

	private static class Device
	{
		public boolean present = false;
		public String deviceType = null;
		public String filename = null;

		@Override
		public String toString()
		{
			return filename + " is " + deviceType + " (present: " + present + ")";
		}
	}

	private static class Controller
	{
		public boolean present = true; // Seems to be implicit, seen at least for IDE...
		public String virtualDev = null;
		Map<String, Device> devices = new HashMap<>();

		@Override
		public String toString()
		{
			return virtualDev + " is (present: " + present + "): " + devices.toString();
		}
	}

}