blob: 7caab74508ac1dab69c7d9ef6c287f2ec5a6eab2 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.utils.qemu;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import com.cloud.storage.Storage;
import com.cloud.utils.script.OutputInterpreter;
import com.cloud.utils.script.Script;
public class QemuImg {
/* The qemu-img binary. We expect this to be in $PATH */
public String _qemuImgPath = "qemu-img";
private int timeout;
/* Shouldn't we have KVMPhysicalDisk and LibvirtVMDef read this? */
public static enum PhysicalDiskFormat {
RAW("raw"), QCOW2("qcow2"), VMDK("vmdk"), FILE("file"), RBD("rbd"), SHEEPDOG("sheepdog"), HTTP("http"), HTTPS("https"), TAR("tar"), DIR("dir");
String format;
private PhysicalDiskFormat(final String format) {
this.format = format;
}
@Override
public String toString() {
return format;
}
}
public static enum PreallocationType {
Off("off"),
Metadata("metadata"),
Full("full");
private final String preallocationType;
private PreallocationType(final String preallocationType){
this.preallocationType = preallocationType;
}
@Override
public String toString(){
return preallocationType;
}
public static PreallocationType getPreallocationType(final Storage.ProvisioningType provisioningType){
switch (provisioningType){
case THIN:
return PreallocationType.Off;
case SPARSE:
return PreallocationType.Metadata;
case FAT:
return PreallocationType.Full;
default:
throw new NotImplementedException();
}
}
}
public QemuImg(final int timeout) {
this.timeout = timeout;
}
public void setTimeout(final int timeout) {
this.timeout = timeout;
}
/**
* Create a QemuImg object
*
*
* @param qemuImgPath
* A alternative path to the qemu-img binary
* @return void
*/
public QemuImg(final String qemuImgPath) {
_qemuImgPath = qemuImgPath;
}
/* These are all methods supported by the qemu-img tool */
/* Perform a consistency check on the disk image */
public void check(final QemuImgFile file) {
}
/**
* Create a new image
*
* This method calls 'qemu-img create'
*
* @param file
* The file to create
* @param backingFile
* A backing file if used (for example with qcow2)
* @param options
* Options for the create. Takes a Map<String, String> with key value
* pairs which are passed on to qemu-img without validation.
* @return void
*/
public void create(final QemuImgFile file, final QemuImgFile backingFile, final Map<String, String> options) throws QemuImgException {
final Script s = new Script(_qemuImgPath, timeout);
s.add("create");
if (options != null && !options.isEmpty()) {
s.add("-o");
final StringBuilder optionsStr = new StringBuilder();
final Iterator<Map.Entry<String, String>> optionsIter = options.entrySet().iterator();
while(optionsIter.hasNext()){
final Map.Entry option = optionsIter.next();
optionsStr.append(option.getKey()).append('=').append(option.getValue());
if(optionsIter.hasNext()){
//Add "," only if there are more options
optionsStr.append(',');
}
}
s.add(optionsStr.toString());
}
/*
-b for a backing file does not show up in the docs, but it works.
Shouldn't this be -o backing_file=filename instead?
*/
s.add("-f");
if (backingFile != null) {
s.add(backingFile.getFormat().toString());
s.add("-b");
s.add(backingFile.getFileName());
} else {
s.add(file.getFormat().toString());
}
s.add(file.getFileName());
if (file.getSize() != 0L) {
s.add(Long.toString(file.getSize()));
} else if (backingFile == null) {
throw new QemuImgException("No size was passed, and no backing file was passed");
}
final String result = s.execute();
if (result != null) {
throw new QemuImgException(result);
}
}
/**
* Create a new image
*
* This method calls 'qemu-img create'
*
* @param file
* The file to create
* @return void
*/
public void create(final QemuImgFile file) throws QemuImgException {
this.create(file, null, null);
}
/**
* Create a new image
*
* This method calls 'qemu-img create'
*
* @param file
* The file to create
* @param backingFile
* A backing file if used (for example with qcow2)
* @return void
*/
public void create(final QemuImgFile file, final QemuImgFile backingFile) throws QemuImgException {
this.create(file, backingFile, null);
}
/**
* Create a new image
*
* This method calls 'qemu-img create'
*
* @param file
* The file to create
* @param options
* Options for the create. Takes a Map<String, String> with key value
* pairs which are passed on to qemu-img without validation.
* @return void
*/
public void create(final QemuImgFile file, final Map<String, String> options) throws QemuImgException {
this.create(file, null, options);
}
/**
* Convert a image from source to destination
*
* This method calls 'qemu-img convert' and takes two objects
* as an argument.
*
*
* @param srcFile
* The source file
* @param destFile
* The destination file
* @param options
* Options for the convert. Takes a Map<String, String> with key value
* pairs which are passed on to qemu-img without validation.
* @return void
*/
public void convert(final QemuImgFile srcFile, final QemuImgFile destFile, final Map<String, String> options) throws QemuImgException {
final Script script = new Script(_qemuImgPath, timeout);
script.add("convert");
// autodetect source format. Sometime int he future we may teach KVMPhysicalDisk about more formats, then we can explicitly pass them if necessary
//s.add("-f");
//s.add(srcFile.getFormat().toString());
script.add("-O");
script.add(destFile.getFormat().toString());
if (options != null && !options.isEmpty()) {
script.add("-o");
final StringBuffer optionsBuffer = new StringBuffer();
for (final Map.Entry<String, String> option : options.entrySet()) {
optionsBuffer.append(option.getKey()).append('=').append(option.getValue()).append(',');
}
String optionsStr = optionsBuffer.toString();
optionsStr = optionsStr.replaceAll(",$", "");
script.add(optionsStr);
}
script.add(srcFile.getFileName());
script.add(destFile.getFileName());
final String result = script.execute();
if (result != null) {
throw new QemuImgException(result);
}
if (srcFile.getSize() < destFile.getSize()) {
this.resize(destFile, destFile.getSize());
}
}
/**
* Convert a image from source to destination
*
* This method calls 'qemu-img convert' and takes two objects
* as an argument.
*
*
* @param srcFile
* The source file
* @param destFile
* The destination file
* @return void
*/
public void convert(final QemuImgFile srcFile, final QemuImgFile destFile) throws QemuImgException {
this.convert(srcFile, destFile, null);
}
/**
* Commit the changes recorded in the file in its base image.
*
* This method calls 'qemu-img commit' and takes one object as
* an argument
*
* @param file
* The file of which changes have to be committed
* @return void
*/
public void commit(final QemuImgFile file) throws QemuImgException {
}
/**
* Execute qemu-img info for the given file
*
* Qemu-img returns human readable output, but this method does it's best
* to turn that into machine readeable data.
*
* Spaces in keys are replaced by underscores (_).
* Sizes (virtual_size and disk_size) are returned in bytes
* Paths (image and backing_file) are the absolute path to the file
*
* @param file
* A QemuImgFile object containing the file to get the information from
* @return A HashMap with String key-value information as returned by 'qemu-img info'
*/
public Map<String, String> info(final QemuImgFile file) throws QemuImgException {
final Script s = new Script(_qemuImgPath);
s.add("info");
s.add(file.getFileName());
final OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser();
final String result = s.execute(parser);
if (result != null) {
throw new QemuImgException(result);
}
final HashMap<String, String> info = new HashMap<String, String>();
final String[] outputBuffer = parser.getLines().trim().split("\n");
for (int i = 0; i < outputBuffer.length; i++) {
final String[] lineBuffer = outputBuffer[i].split(":", 2);
if (lineBuffer.length == 2) {
final String key = lineBuffer[0].trim().replace(" ", "_");
String value = null;
if (key.equals("virtual_size")) {
value = lineBuffer[1].trim().replaceAll("^.*\\(([0-9]+).*$", "$1");
} else {
value = lineBuffer[1].trim();
}
info.put(key, value);
}
}
return info;
}
/* List, apply, create or delete snapshots in image */
public void snapshot() throws QemuImgException {
}
/* Changes the backing file of an image */
public void rebase() throws QemuImgException {
}
/**
* Resize an image
*
* This method simple calls 'qemu-img resize'.
* A negative size value will get prefixed with - and a positive with +
*
* Sizes are in bytes and will be passed on that way
*
* @param file
* The file to resize
* @param size
* The new size
* @param delta
* Flag if the new size is a delta
*/
public void resize(final QemuImgFile file, final long size, final boolean delta) throws QemuImgException {
String newSize = null;
if (size == 0) {
throw new QemuImgException("size should never be exactly zero");
}
if (delta) {
if (size > 0) {
newSize = "+" + Long.toString(size);
} else {
newSize = Long.toString(size);
}
} else {
if (size <= 0) {
throw new QemuImgException("size should not be negative if 'delta' is false!");
}
newSize = Long.toString(size);
}
final Script s = new Script(_qemuImgPath);
s.add("resize");
s.add(file.getFileName());
s.add(newSize);
s.execute();
}
/**
* Resize an image
*
* This method simple calls 'qemu-img resize'.
* A negative size value will get prefixed with - and a positive with +
*
* Sizes are in bytes and will be passed on that way
*
* @param file
* The file to resize
* @param size
* The new size
*/
public void resize(final QemuImgFile file, final long size) throws QemuImgException {
this.resize(file, size, false);
}
}