package org.openslx.dozmod.gui.wizard.page;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.apache.commons.io.FilenameUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.openslx.bwlp.thrift.iface.ImageDetailsRead;
import org.openslx.bwlp.thrift.iface.Virtualizer;
import org.openslx.dozmod.Branding;
import org.openslx.dozmod.Config;
import org.openslx.dozmod.gui.Gui;
import org.openslx.dozmod.gui.helper.I18n;
import org.openslx.dozmod.gui.helper.MessageType;
import org.openslx.dozmod.gui.helper.QFileChooser;
import org.openslx.dozmod.gui.wizard.Wizard;
import org.openslx.dozmod.gui.wizard.layout.ImageUploadPageLayout;
import org.openslx.dozmod.state.UploadWizardState;
import org.openslx.dozmod.thrift.Session;
import org.openslx.dozmod.thrift.ThriftActions;
import org.openslx.dozmod.thrift.ThriftError;
import org.openslx.dozmod.thrift.UploadInitiator;
import org.openslx.dozmod.thrift.WrappedException;
import org.openslx.dozmod.thrift.cache.MetaDataCache;
import org.openslx.dozmod.util.FormatHelper;
import org.openslx.sat.thrift.version.Feature;
import org.openslx.thrifthelper.TConst;
import org.openslx.virtualization.configuration.VirtualizationConfigurationVmware;
import org.openslx.virtualization.configuration.data.ConfigurationDataDozModClientToDozModServer;
import org.openslx.virtualization.configuration.logic.ConfigurationLogicDozModClientToDozModServer;
import org.openslx.virtualization.configuration.transformation.TransformationException;
import org.openslx.virtualization.disk.DiskImage;
import org.openslx.virtualization.disk.DiskImageException;
import org.openslx.virtualization.disk.DiskImage.ImageFormat;
import org.openslx.virtualization.configuration.VirtualizationConfiguration;
import org.openslx.virtualization.configuration.VirtualizationConfiguration.HardDisk;
import org.openslx.virtualization.configuration.VirtualizationConfigurationException;
import org.openslx.virtualization.configuration.VirtualizationConfigurationQemu;
import org.openslx.virtualization.configuration.VirtualizationConfigurationVirtualBox;
/**
* Page for uploading a new image.
*/
public class ImageUploadPage extends ImageUploadPageLayout {
/**
* Version for serialization.
*/
private static final long serialVersionUID = -2974001512014608455L;
private final static Logger LOGGER = LogManager.getLogger(ImageUploadPage.class);
private UploadWizardState state;
private String lastDetectedName = null;
private ImageDetailsRead existingImage = null;
private final FileNameExtensionFilter allSupportedFilter;
private final FileNameExtensionFilter vmxFilter = new FileNameExtensionFilter(
"VMware Virtual Machine", VirtualizationConfigurationVmware.FILE_NAME_EXTENSION);
private final FileNameExtensionFilter vboxFilter = new FileNameExtensionFilter(
"VirtualBox Virtual Machine", VirtualizationConfigurationVirtualBox.FILE_NAME_EXTENSION);
private final FileNameExtensionFilter qemuFilter = new FileNameExtensionFilter(
"QEMU Virtual Machine", VirtualizationConfigurationQemu.FILE_NAME_EXTENSION);
private final FileNameExtensionFilter ovfFilter = new FileNameExtensionFilter(
"OVF Virtual Machine Format", "ovf");
private final FileNameExtensionFilter ovaFilter = new FileNameExtensionFilter(
"OVA Virtual Machine Format", "ova");
public ImageUploadPage(Wizard wizard, UploadWizardState uploadWizardState,
final ImageDetailsRead existingImage) {
super(wizard);
setPageComplete(false);
this.canComeBack = false;
this.state = uploadWizardState;
this.existingImage = existingImage;
lblImageName.setVisible(existingImage == null);
txtImageName.setVisible(existingImage == null);
txtInfoText.setVisible(existingImage == null);
// show the licensed software checkbox since we are uploading new version
chkLicenseRestricted.setVisible(existingImage != null);
chkLicenseRestricted.setSelected(existingImage != null); // TODO selected by default?
// Browse for *.vmx
btnBrowseForImage.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
browseForVm();
}
});
txtImageFile.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 1)
browseForVm();
}
});
// initialize allSupportedFilter depending on whether multiple hypervisors
// are supported or not
if (Session.hasFeature(Feature.MULTIPLE_HYPERVISORS)) {
allSupportedFilter = new FileNameExtensionFilter("All Supported",
VirtualizationConfigurationVmware.FILE_NAME_EXTENSION,
VirtualizationConfigurationVirtualBox.FILE_NAME_EXTENSION,
VirtualizationConfigurationQemu.FILE_NAME_EXTENSION,
"ovf", "ova");
} else {
allSupportedFilter = new FileNameExtensionFilter("All Supported",
VirtualizationConfigurationVmware.FILE_NAME_EXTENSION,
"ovf", "ova");
}
btnBrowseForImage.requestFocus();
}
private void browseForVm() {
QFileChooser fc = new QFileChooser(Config.getUploadPath(), false);
fc.setAcceptAllFileFilterUsed(false);
fc.addChoosableFileFilter(vmxFilter);
fc.addChoosableFileFilter(ovfFilter);
fc.addChoosableFileFilter(ovaFilter);
if (Session.hasFeature(Feature.MULTIPLE_HYPERVISORS)) {
fc.addChoosableFileFilter(vboxFilter);
fc.addChoosableFileFilter(qemuFilter);
}
fc.addChoosableFileFilter(allSupportedFilter);
fc.setFileFilter(allSupportedFilter);
// differentiate between existing and new VMs
if (existingImage != null) {
if (existingImage.virtId.equals(TConst.VIRT_VMWARE)) {
fc.setFileFilter(vmxFilter);
} else if (existingImage.virtId.equals(TConst.VIRT_VIRTUALBOX)) {
fc.setFileFilter(vboxFilter);
} else if (existingImage.virtId.equals(TConst.VIRT_QEMU)) {
fc.setFileFilter(qemuFilter);
}
}
int action = fc.showOpenDialog(getDialog());
File file = fc.getSelectedFile();
if (action == JFileChooser.APPROVE_OPTION) {
// If the vm was exported via vmware fusion on macos it might be
// a vmwarevm which is a directory containing the vm files within.
if (file != null && file.isDirectory()) {
try {
File[] vmxfiles = file.listFiles(new FilenameFilter() {
public boolean accept(File dir, String filename) {
return filename.endsWith(".vmx");
}
});
// There should be only one vmx file in the directory
if (vmxfiles.length < 1) {
// TODO translate
Gui.showMessageBox(this,
I18n.PAGE.getString("ImageUpload.WizardPage.errorMessage.noVmxInDir"),
MessageType.ERROR, LOGGER, null);
return;
} else if (vmxfiles.length >= 2) {
Gui.showMessageBox(this,
I18n.PAGE.getString(
"ImageUpload.WizardPage.errorMessage.multipleVmxInDirFound"),
MessageType.ERROR, LOGGER, null);
}
if (vmxfiles[0] != null && vmxfiles[0].isFile())
file = vmxfiles[0];
} catch (Exception e) {
Gui.showMessageBox(this,
I18n.PAGE.getString(
"ImageUpload.WizardPage.errorMessage.errorWhileSearchingForVmx"),
MessageType.ERROR, LOGGER, e);
return;
}
}
}
if (action != JFileChooser.APPROVE_OPTION || file == null)
return;
vmSelected(file.getAbsoluteFile());
}
private void askForConversion(File file) {
int dialogButton = JOptionPane.YES_NO_OPTION;
int dialogResult = JOptionPane.showConfirmDialog(this,
I18n.PAGE.getString("ImageUpload.WizardPage.dialog.OvfOvaDetected"),
I18n.PAGE.getString("ImageUpload.WizardPage.dialog.title"), dialogButton);
if (dialogResult == 0) {
state.descriptionFile = file;
setErrorMessage(null);
setDescription("Bitte starten Sie die Konvertierung.");
wizard.showOutOfOrderPage(state.conversionPage);
} else {
setPageComplete(false);
}
}
private void vmSelected(File file) {
Config.setUploadPath(file.getParent());
txtImageFile.setText("");
txtImageName.setText("");
// ask to convert OVA and OVF files
String fileExtension = FilenameUtils.getExtension(file.getAbsolutePath());
if (fileExtension.equalsIgnoreCase("ova") || fileExtension.equalsIgnoreCase("ovf")) {
askForConversion(file);
return;
}
try {
// gets the metadata object of the selected VM depending on its type
state.virtualizationConfig = VirtualizationConfiguration.getInstance(MetaDataCache.getOperatingSystems(), file);
} catch (IOException e) {
Gui.showMessageBox(this,
I18n.PAGE.getString("ImageUpload.Message.error.couldNotGetMetadata", file.getPath()),
MessageType.ERROR, LOGGER, e);
setPageComplete(false);
return;
}
// See if the config is declared valid
try {
state.virtualizationConfig.validate();
} catch (VirtualizationConfigurationException e1) {
boolean choice = Gui.showMessageBox(this,
I18n.PAGE.getString("ImageUpload.Message.warning.virtConfigNotValid"),
MessageType.WARNING_RETRY, LOGGER, e1);
if (!choice)
return;
}
try {
// transforms (filters) the virtualization configuration
final ConfigurationLogicDozModClientToDozModServer uploadLogic = new ConfigurationLogicDozModClientToDozModServer();
uploadLogic.apply( state.virtualizationConfig, new ConfigurationDataDozModClientToDozModServer() );
} catch (TransformationException e) {
setErrorMessage(I18n.PAGE.getString("ImageUpload.WizardPage.errorMessage.invalidConfigFile"));
setPageComplete(false);
return;
}
final String virtualizerName = state.virtualizationConfig.getVirtualizer().getName();
// bail if multiple hypervisors are not supported
if (!(state.virtualizationConfig instanceof VirtualizationConfigurationVmware) && !Session.hasFeature(Feature.MULTIPLE_HYPERVISORS)) {
setErrorMessage(I18n.PAGE.getString("ImageUpload.WizardPage.errorMessage.hypervisorNotSupported",
virtualizerName));
setPageComplete(false);
return;
}
// check if the user somehow changed the type of the VM
if (existingImage != null && !existingImage.virtId.equals(state.virtualizationConfig.getVirtualizer().getId())) {
Virtualizer existingImageVirtualizer = MetaDataCache.getVirtualizerById(existingImage.virtId);
String existingImageVirtualizerName = "<error>";
if (existingImageVirtualizer != null)
existingImageVirtualizerName = existingImageVirtualizer.virtName;
setErrorMessage(I18n.PAGE.getString("ImageUpload.WizardPage.errorMessage.VMTypeChanged",
existingImageVirtualizerName));
setPageComplete(false);
return;
}
List<HardDisk> hdds = state.virtualizationConfig.getHdds();
if (hdds.size() == 0 || hdds.get(0).diskImage == null) {
setErrorMessage(I18n.PAGE.getString("ImageUpload.WizardPage.errorMessage.noHDD", virtualizerName));
setPageComplete(false);
return;
}
if (hdds.size() > 1) {
setErrorMessage(
I18n.PAGE.getString("ImageUpload.WizardPage.errorMessage.moreThanOneHDD", virtualizerName));
setPageComplete(false);
return;
// Allow to continue!?
}
// now check the disk files
File vmDiskFileInfo = new File(hdds.get(0).diskImage);
if (!vmDiskFileInfo.isAbsolute()) {
// it's relative, compose path using the vmx location
File vmBaseDirectory = file.getParentFile();
vmDiskFileInfo = new File(vmBaseDirectory, hdds.get(0).diskImage);
}
final boolean diskImageIsSnapshot;
final boolean diskImageIsStandalone;
final ImageFormat diskFormat;
try (DiskImage diskImage = DiskImage.newInstance(vmDiskFileInfo)) {
diskImageIsSnapshot = diskImage.isSnapshot();
diskImageIsStandalone = diskImage.isStandalone();
diskFormat = diskImage.getFormat();
} catch (FileNotFoundException e) {
setErrorMessage(I18n.PAGE.getString("ImageUpload.WizardPage.errorMessage.diskImageNotFound",
vmDiskFileInfo.getName()));
setPageComplete(false);
return;
} catch (IOException e) {
setErrorMessage(I18n.PAGE.getString("ImageUpload.WizardPage.errorMessage.diskImageNotReadable",
vmDiskFileInfo.getName()));
setPageComplete(false);
return;
} catch (DiskImageException e) {
setErrorMessage(
I18n.PAGE.getString("ImageUpload.WizardPage.errorMessage.diskImageHasUnknownFormat",
vmDiskFileInfo.getName()));
LOGGER.debug("Selected disk file has unknown format.", e);
setPageComplete(false);
return;
}
// check if disk image format is supported by the hypervisor's supported disk image formats
final List<ImageFormat> supportedImageFormats = state.virtualizationConfig.getVirtualizer()
.getSupportedImageFormats();
if (!diskFormat.isSupportedbyVirtualizer( supportedImageFormats )) {
Gui.showMessageBox(I18n.PAGE.getString("ImageUpload.Message.warning.diskImageNotSupportedByHypervisor",
Branding.getServiceName()), MessageType.WARNING, null, null);
setErrorMessage(I18n.PAGE.getString("ImageUpload.WizardPage.errorMessage.diskImageNotSupportedByHypervisor"));
setPageComplete(false);
return;
}
// Warn user about snapshot
if (diskImageIsSnapshot || state.virtualizationConfig.isMachineSnapshot()) {
Gui.showMessageBox(I18n.PAGE.getString("ImageUpload.Message.warning.diskImageSnapshot",
Branding.getServiceName()), MessageType.WARNING, null, null);
setErrorMessage(I18n.PAGE.getString("ImageUpload.WizardPage.errorMessage.diskImageSnapshot"));
setPageComplete(false);
return;
}
if (!diskImageIsStandalone) {
Gui.showMessageBox(
I18n.PAGE.getString("ImageUpload.Message.warning.diskImageStandalone", Branding.getApplicationName()),
MessageType.WARNING, null, null);
setErrorMessage(I18n.PAGE.getString("ImageUpload.WizardPage.errorMessage.diskImageStandalone"));
setPageComplete(false);
return;
}
// Check imposed size limit by server
if (!Session.isSuperUser()) {
long maxSize = Session.getSatelliteConfig().vmSizeLimit;
if (maxSize > 0 && maxSize < vmDiskFileInfo.length()) {
Gui.showMessageBox(
I18n.PAGE.getString("ImageUpload.Message.warning.diskImageTooBig",
FormatHelper.bytes(maxSize, false),
FormatHelper.bytes(vmDiskFileInfo.length(), false)),
MessageType.WARNING, null, null);
setErrorMessage(I18n.PAGE.getString("ImageUpload.WizardPage.errorMessage.diskImageTooBig"));
setPageComplete(false);
return;
}
}
// everything seems fine so far
state.diskFile = vmDiskFileInfo;
state.descriptionFile = file;
if (existingImage == null) {
// User didn't enter a name yet or didn't change it -> set
String imageName = txtImageName.getText();
if (imageName.isEmpty() || imageName.equals(lastDetectedName)) {
txtImageName.setText(state.virtualizationConfig.getDisplayName());
}
}
lastDetectedName = state.virtualizationConfig.getDisplayName();
state.detectedOs = state.virtualizationConfig.getOs();
txtImageFile.setText(file.getAbsolutePath());
// let the user know the upload is ready
setErrorMessage(null);
setDescription(I18n.PAGE.getString("ImageUpload.WizardPage.description"));
setPageComplete(true);
}
private boolean askCancelLockFile(String... lockFiles) {
for (String lockFile : lockFiles) {
File file = new File(lockFile);
if (!file.exists())
continue;
return !Gui.showMessageBox(this, I18n.PAGE.getString("ImageUpload.Message.yesNo.cancelLockFile"),
MessageType.QUESTION_YESNO, null, null);
}
return false;
}
/**
* This function starts the image creation process. It is triggered by the
* "Next" button.
*
* Depending on the state, it will first try to get a UUID for the new image by
* calling createImage() of the thrift API. If a UUID is received, it will
* request an upload with requestImageVersionUpload(). If granted, it will
* finally start a thread for the UploadTask.
*
* Then a callback to the Gui is executed where we can process the upload state
* and give the user feedback about it.
*
*/
@Override
protected boolean wantNextOrFinish() {
// Check for vmware player lock files - warn user if found, might corrupt upload
if (askCancelLockFile(state.descriptionFile.getAbsolutePath() + ".lck",
state.diskFile.getAbsolutePath() + ".lck")) {
setErrorMessage(I18n.PAGE.getString("ImageUpload.WizardPage.errorMessage.cancelLockFile"));
return false;
}
// are we creating a new image? then either:
// get the image name either auto filled by VmwareMetaData or by user
// get the image name from the image we are uploading a new version of
state.name = existingImage != null ? existingImage.getImageName() : txtImageName.getText();
// -- create image to get uuid --
if (existingImage == null) {
if (state.uuid == null) {
state.uuid = ThriftActions.createImage(JOptionPane.getFrameForComponent(this), state.name);
if (state.uuid == null)
return false;
txtImageName.setEnabled(false);
btnBrowseForImage.setEnabled(false);
txtImageFile.setEnabled(false);
}
} else {
state.uuid = existingImage.getImageBaseId();
}
// Create upload initiator that will manage requesting a token, hashing the
// file, connecting for upload...
if (state.upload == null) {
try {
state.upload = new UploadInitiator(state.uuid, state.diskFile,
ByteBuffer.wrap(state.virtualizationConfig.getConfigurationAsByteArray()));
} catch (WrappedException e) {
ThriftError.showMessage(this, LOGGER, e.exception, e.displayMessage);
return false;
} catch (IOException e) {
Gui.showMessageBox(this,
I18n.PAGE.getString("ImageUpload.Message.error.uploadInitiatorFailed"),
MessageType.ERROR, LOGGER, e);
return false;
}
}
// Start the hash check now
state.upload.startHashing();
return true;
}
@Override
protected void onPageEnter() {
super.onPageEnter();
// When entering from the conversion page the user probably wants to upload the
// new vmx file
if (state.convertedDescriptionFile != null)
vmSelected(state.convertedDescriptionFile);
}
}