summaryrefslogtreecommitdiffstats
path: root/core/modules/qemu/runvirt-plugin-qemu/src/main/java/org/openslx/runvirt/plugin/qemu/configuration/TransformationSpecificQemuFirmware.java
blob: 6be0368e9fdbb0f09bed2268e00c289b85cfd334 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
package org.openslx.runvirt.plugin.qemu.configuration;

import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.function.Predicate;

import org.openslx.libvirt.domain.Domain;
import org.openslx.runvirt.plugin.qemu.cmdln.CommandLineArgs;
import org.openslx.runvirt.plugin.qemu.virtualization.LibvirtHypervisorQemu;
import org.openslx.util.LevenshteinDistance;
import org.openslx.virtualization.configuration.transformation.TransformationException;
import org.openslx.virtualization.configuration.transformation.TransformationSpecific;

import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.annotations.SerializedName;

class QemuFirmware
{
	@SerializedName( "description" )
	private String description;
	@SerializedName( "interface-types" )
	private ArrayList<String> interfaceTypes;
	@SerializedName( "mapping" )
	private QemuFirmwareMapping mapping;
	@SerializedName( "targets" )
	private ArrayList<QemuFirmwareTarget> targets;
	@SerializedName( "features" )
	private ArrayList<String> features;
	@SerializedName( "tags" )
	private ArrayList<String> tags;

	public String getDescription()
	{
		return description;
	}

	public ArrayList<String> getInterfaceTypes()
	{
		return interfaceTypes;
	}

	public QemuFirmwareMapping getMapping()
	{
		return mapping;
	}

	public ArrayList<QemuFirmwareTarget> getTargets()
	{
		return targets;
	}

	public ArrayList<String> getFeatures()
	{
		return features;
	}

	public ArrayList<String> getTags()
	{
		return tags;
	}
}

class QemuFirmwareMapping
{
	@SerializedName( "device" )
	private String device;
	@SerializedName( "executable" )
	private QemuFirmwareMappingExecutable executable;
	@SerializedName( "nvram-template" )
	private QemuFirmwareMappingNvramTemplate nvramTemplate;

	public String getDevice()
	{
		return device;
	}

	public QemuFirmwareMappingExecutable getExecutable()
	{
		return executable;
	}

	public QemuFirmwareMappingNvramTemplate getNvramTemplate()
	{
		return nvramTemplate;
	}
}

class QemuFirmwareMappingExecutable
{
	@SerializedName( "filename" )
	private String fileName;
	@SerializedName( "format" )
	private String format;

	public String getFileName()
	{
		return fileName;
	}

	public String getFormat()
	{
		return format;
	}
}

class QemuFirmwareMappingNvramTemplate
{
	@SerializedName( "filename" )
	private String fileName;
	@SerializedName( "format" )
	private String format;

	public String getFileName()
	{
		return fileName;
	}

	public String getFormat()
	{
		return format;
	}
}

class QemuFirmwareTarget
{
	@SerializedName( "architecture" )
	private String architecture;
	@SerializedName( "machines" )
	private ArrayList<String> machines;

	public String getArchitecture()
	{
		return architecture;
	}

	public ArrayList<String> getMachines()
	{
		return machines;
	}
}

/**
 * Specific firmware transformation for Libvirt/QEMU virtualization configurations.
 * 
 * @author Manuel Bentele
 * @version 1.0
 */
public class TransformationSpecificQemuFirmware
		extends TransformationSpecific<Domain, CommandLineArgs, LibvirtHypervisorQemu>
{
	/**
	 * Name of the configuration transformation.
	 */
	private static final String NAME = "QEMU Firmware [Seabios, UEFI (OVMF)]";

	/**
	 * Creates a new firmware transformation for Libvirt/QEMU virtualization configurations.
	 * 
	 * @param hypervisor Libvirt/QEMU hypervisor.
	 */
	public TransformationSpecificQemuFirmware( LibvirtHypervisorQemu virtualizer )
	{
		super( TransformationSpecificQemuFirmware.NAME, virtualizer );
	}

	/**
	 * Validates a virtualization configuration and input arguments for this transformation.
	 * 
	 * @param config virtualization configuration for the validation.
	 * @param args input arguments for the validation.
	 * @throws TransformationException validation has failed.
	 */
	private void validateInputs( Domain config, CommandLineArgs args ) throws TransformationException
	{
		if ( config == null || args == null ) {
			throw new TransformationException( "Virtualization configuration or input arguments are missing!" );
		} else if ( args.getFirmware() == null || args.getFirmware().isEmpty() ) {
			throw new TransformationException( "Path to QEMU firmware specifications directory is not specified!" );
		}
	}

	private String lookupTargetOsLoader( String firmwareSpecDirectory, String sourceOsLoader, String sourceOsArch,
			String sourceOsMachine )
			throws TransformationException
	{
		String lookupOsLoader = null;

		// parse and check firmware specification directory
		final File fwSpecDirectory = new File( firmwareSpecDirectory );
		if ( !fwSpecDirectory.exists() || !fwSpecDirectory.isDirectory() ) {
			throw new TransformationException( "Path to QEMU firmware specifications directory is invalid!" );
		}

		// get all firmware specification files
		final FileFilter fwSpecFilesFilter = file -> !file.isDirectory() && file.getName().endsWith( ".json" );
		final File[] fwSpecFiles = fwSpecDirectory.listFiles( fwSpecFilesFilter );

		// get paths to firmware files from firmware specification files
		if ( fwSpecFiles != null ) {
			final ArrayList<QemuFirmware> uefiFirmwares = new ArrayList<QemuFirmware>();
			final Gson gson = new Gson();
			for ( final File fwSpecFile : fwSpecFiles ) {
				QemuFirmware firmware = null;

				try {
					final Reader jsonContent = new FileReader( fwSpecFile );
					firmware = gson.fromJson( jsonContent, QemuFirmware.class );
				} catch ( FileNotFoundException | JsonSyntaxException | JsonIOException e ) {
					throw new TransformationException( e.getLocalizedMessage() );
				}

				final Predicate<String> byInterfaceType = s -> s.toLowerCase().equals( "uefi" );
				if ( firmware.getInterfaceTypes().stream().filter( byInterfaceType ).findAny().isPresent() ) {
					// found valid UEFI firmware
					// check if architecture and machine type of the VM is supported by the firmware
					final Predicate<QemuFirmwareTarget> byArchitecture = t -> sourceOsArch.equals( t.getArchitecture() );
					final Predicate<String> byMachineType = s -> sourceOsMachine.startsWith( s.replace( "*", "" ) );
					final Predicate<QemuFirmwareTarget> byMachines = t -> t.getMachines().stream().filter( byMachineType )
							.findAny().isPresent();

					if ( firmware.getTargets().stream().filter( byArchitecture ).filter( byMachines ).findAny()
							.isPresent() ) {
						// found UEFI firmware supporting suitable architecture and machine type from VM
						uefiFirmwares.add( firmware );
					}
				}
			}

			if ( uefiFirmwares.isEmpty() ) {
				throw new TransformationException( "There aren't any suitable UEFI firmwares locally available!" );
			} else {
				final LevenshteinDistance distance = new LevenshteinDistance( 1, 1, 1 );
				int minFileNameDistance = Integer.MAX_VALUE;
				Path suitablestUefiFirmwarePath = null;

				for ( final QemuFirmware uefiFirmware : uefiFirmwares ) {
					final Path uefiFirmwarePath = Paths.get( uefiFirmware.getMapping().getExecutable().getFileName() );
					final Path sourceOsLoaderPath = Paths.get( sourceOsLoader );
					final String uefiFirmwareFileName = uefiFirmwarePath.getFileName().toString().toLowerCase();
					final String sourceOsLoaderFileName = sourceOsLoaderPath.getFileName().toString().toLowerCase();

					final int fileNameDistance = distance.calculateDistance( uefiFirmwareFileName, sourceOsLoaderFileName );
					if ( fileNameDistance < minFileNameDistance ) {
						minFileNameDistance = fileNameDistance;
						suitablestUefiFirmwarePath = uefiFirmwarePath;
					}
				}

				lookupOsLoader = suitablestUefiFirmwarePath.toString();
			}
		}

		return lookupOsLoader;
	}

	@Override
	public void transform( Domain config, CommandLineArgs args ) throws TransformationException
	{
		// validate configuration and input arguments
		this.validateInputs( config, args );

		// get OS loader from VM
		final String sourceOsLoader = config.getOsLoader();

		// get OS architecture from VM
		final String sourceOsArch = config.getOsArch();

		// get OS machine type from VM
		final String sourceOsMachine = config.getOsMachine();

		// check if OS loader is specified
		if ( sourceOsLoader != null && !sourceOsLoader.isEmpty() ) {
			// OS loader is specified so transform path to specified firmware path
			final String targetOsLoader = this.lookupTargetOsLoader( args.getFirmware(), sourceOsLoader, sourceOsArch,
					sourceOsMachine );
			if ( targetOsLoader != null && !targetOsLoader.isEmpty() ) {
				config.setOsLoader( targetOsLoader );
			}
		}
	}
}