package org.ovirt.engine.core.bll.provider.storage; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.ovirt.engine.core.bll.provider.ProviderProxyFactory; import org.ovirt.engine.core.common.businessentities.OpenStackImageProviderProperties; import org.ovirt.engine.core.common.businessentities.Provider; import org.ovirt.engine.core.common.businessentities.StorageDomainStatic; import org.ovirt.engine.core.common.businessentities.StorageDomainType; import org.ovirt.engine.core.common.businessentities.storage.DiskImage; import org.ovirt.engine.core.common.businessentities.storage.ImageFileType; import org.ovirt.engine.core.common.businessentities.storage.RepoImage; import org.ovirt.engine.core.common.businessentities.storage.StorageType; import org.ovirt.engine.core.common.businessentities.storage.VolumeFormat; import org.ovirt.engine.core.compat.Guid; import com.woorea.openstack.base.client.OpenStackRequest; import com.woorea.openstack.glance.Glance; import com.woorea.openstack.glance.model.Image; import com.woorea.openstack.glance.model.ImageDownload; import com.woorea.openstack.glance.model.Images; public class OpenStackImageProviderProxy extends AbstractOpenStackStorageProviderProxy<Glance, OpenStackImageProviderProperties, GlanceProviderValidator> { enum GlanceImageFormat { RAW("raw"), ISO("iso"), COW("qcow2"); private String value; GlanceImageFormat(String value) { this.value = value; } public String getValue() { return value; } } enum GlanceImageContainer { BARE("bare"); private String value; GlanceImageContainer(String value) { this.value = value; } public String getValue() { return value; } } private static final String API_VERSION = "/v1"; private static final int QCOW2_SIGNATURE = 0x514649fb; private static final int QCOW2_VERSION = 2; private static final int QCOW2_SIZE_OFFSET = 24; public OpenStackImageProviderProxy(Provider<OpenStackImageProviderProperties> provider) { this.provider = provider; } @Override public void onAddition() { addStorageDomain(StorageType.GLANCE, StorageDomainType.Image); } public static OpenStackImageProviderProxy getFromStorageDomainId(Guid storageDomainId) { StorageDomainStatic storageDomainStatic = getDbFacade().getStorageDomainStaticDao().get(storageDomainId); if (storageDomainStatic != null) { Provider<?> provider = getDbFacade().getProviderDao().get(new Guid(storageDomainStatic.getStorage())); return ProviderProxyFactory.getInstance().create(provider); } return null; } @Override protected Glance getClient() { if (client == null) { client = new Glance(getProvider().getUrl() + API_VERSION); client.setTokenProvider(getTokenProvider()); } return client; } protected void validateContainerFormat(Image glanceImage) { if (!glanceImage.getContainerFormat().equals(GlanceImageContainer.BARE.getValue())) { throw new OpenStackImageException( OpenStackImageException.ErrorType.UNSUPPORTED_CONTAINER_FORMAT, "Unsupported container format: " + glanceImage.getContainerFormat()); } } private static RepoImage imageToRepoImage(Image glanceImage) { RepoImage repoImage = new RepoImage(); repoImage.setSize(glanceImage.getSize()); repoImage.setDateCreated(null); repoImage.setRepoImageId(glanceImage.getId()); repoImage.setRepoImageName(glanceImage.getName()); if (glanceImage.getContainerFormat() == null || glanceImage.getDiskFormat() == null || !glanceImage.getContainerFormat().equals(GlanceImageContainer.BARE.getValue())) { repoImage.setFileType(ImageFileType.Unknown); } else { if (glanceImage.getDiskFormat().equals(GlanceImageFormat.RAW.getValue()) || glanceImage.getDiskFormat().equals(GlanceImageFormat.COW.getValue())) { repoImage.setFileType(ImageFileType.Disk); } else if (glanceImage.getDiskFormat().equals(GlanceImageFormat.ISO.getValue())) { repoImage.setFileType(ImageFileType.ISO); } else { repoImage.setFileType(ImageFileType.Unknown); } } return repoImage; } public List<RepoImage> getAllImagesAsRepoImages(Integer listSize, Integer totalListSize) { ArrayList<RepoImage> repoImages = new ArrayList<>(); long currentTime = System.currentTimeMillis(); Images images = null; do { OpenStackRequest<Images> listRequest = getClient().images() .list(true) .queryParam("limit", listSize) .queryParam("sort_key", "name") .queryParam("sort_dir", "asc"); if (images != null) { listRequest.queryParam("marker", images.getList().get(images.getList().size() - 1).getId()); } images = listRequest.execute(); for (Image glanceImage : images) { RepoImage repoImage = imageToRepoImage(glanceImage); repoImage.setLastRefreshed(currentTime); repoImages.add(repoImage); } } while((images.getList().size() >= listSize) && (totalListSize != null && repoImages.size() < totalListSize)); return repoImages; } public DiskImage getImageAsDiskImage(String id) { DiskImage diskImage = new DiskImage(); Image glanceImage = getClient().images().show(id).execute(); validateContainerFormat(glanceImage); String shortHash = glanceImage.getId().substring(0, 7); if (glanceImage.getName() != null) { diskImage.setDiskDescription(glanceImage.getName() + " (" + shortHash + ")"); } else { diskImage.setDiskDescription("Glance disk: " + shortHash); } diskImage.setSize(getImageVirtualSize(glanceImage)); diskImage.setActualSizeInBytes(glanceImage.getSize()); if (glanceImage.getDiskFormat().equals(GlanceImageFormat.RAW.getValue())) { diskImage.setVolumeFormat(VolumeFormat.RAW); } else if (glanceImage.getDiskFormat().equals(GlanceImageFormat.COW.getValue())) { diskImage.setVolumeFormat(VolumeFormat.COW); } else { throw new OpenStackImageException( OpenStackImageException.ErrorType.UNSUPPORTED_DISK_FORMAT, "Unknown disk format: " + glanceImage.getDiskFormat()); } return diskImage; } public String createImageFromDiskImage(DiskImage diskImage) { Image glanceImage = new Image(); glanceImage.setName(diskImage.getDiskAlias()); if (diskImage.getVolumeFormat() == VolumeFormat.RAW) { glanceImage.setDiskFormat(GlanceImageFormat.RAW.getValue()); } else if (diskImage.getVolumeFormat() == VolumeFormat.COW) { glanceImage.setDiskFormat(GlanceImageFormat.COW.getValue()); } else { throw new OpenStackImageException( OpenStackImageException.ErrorType.UNSUPPORTED_DISK_FORMAT, "Unknown disk format: " + diskImage.getVolumeFormat()); } glanceImage.setContainerFormat(GlanceImageContainer.BARE.getValue()); Image retGlanceImage = getClient().images().create(glanceImage).execute(); return retGlanceImage.getId(); } private long getCowVirtualSize(String id) { // For the qcow2 format we need to download the image header and read the virtual size from there byte[] imgContent = new byte[72]; ImageDownload downloadImage = getClient().images().download(id).execute(); try (InputStream inputStream = downloadImage.getInputStream()) { int bytesRead = inputStream.read(imgContent, 0, imgContent.length); if (bytesRead != imgContent.length) { throw new OpenStackImageException( OpenStackImageException.ErrorType.UNABLE_TO_DOWNLOAD_IMAGE, "Unable to read image header: " + bytesRead); } } catch (IOException e) { throw new OpenStackImageException( OpenStackImageException.ErrorType.UNABLE_TO_DOWNLOAD_IMAGE, "Unable to download image"); } ByteBuffer b = ByteBuffer.wrap(imgContent); if (b.getInt() == QCOW2_SIGNATURE && b.getInt() == QCOW2_VERSION) { b.position(QCOW2_SIZE_OFFSET); return b.getLong(); } throw new OpenStackImageException( OpenStackImageException.ErrorType.UNRECOGNIZED_IMAGE_FORMAT, "Unable to recognize QCOW2 format"); } protected long getImageVirtualSize(Image glanceImage) { validateContainerFormat(glanceImage); if (glanceImage.getDiskFormat().equals(GlanceImageFormat.RAW.getValue()) || glanceImage.getDiskFormat().equals(GlanceImageFormat.ISO.getValue())) { return glanceImage.getSize(); } else if (glanceImage.getDiskFormat().equals(GlanceImageFormat.COW.getValue())) { return getCowVirtualSize(glanceImage.getId()); } throw new OpenStackImageException( OpenStackImageException.ErrorType.UNSUPPORTED_DISK_FORMAT, "Unknown disk format: " + glanceImage.getDiskFormat()); } public Map<String, String> getDownloadHeaders() { HashMap<String, String> httpHeaders = new HashMap<>(); if (getTokenProvider() != null) { httpHeaders.put("X-Auth-Token", getTokenProvider().getToken()); } return httpHeaders; } public Map<String, String> getUploadHeaders() { Map<String, String> httpHeaders = getDownloadHeaders(); httpHeaders.put("Content-Type", "application/octet-stream"); return httpHeaders; } public String getImageUrl(String id) { return getProvider().getUrl() + API_VERSION + "/images/" + id; } @Override public GlanceProviderValidator getProviderValidator() { if (providerValidator == null) { providerValidator = new GlanceProviderValidator(provider); } return providerValidator; } }