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.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.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; } 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 = ""; if (existingImageVirtualizer != null) existingImageVirtualizerName = existingImageVirtualizer.virtName; setErrorMessage(I18n.PAGE.getString("ImageUpload.WizardPage.errorMessage.VMTypeChanged", existingImageVirtualizerName)); setPageComplete(false); return; } List 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 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; } // 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); } }