blob: c05d8b3ae08a3ca794d14effa9e022f0241c49d3 [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
// with 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 com.cloud.hypervisor.kvm.storage;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import com.cloud.agent.api.to.DiskTO;
import com.cloud.storage.Storage;
import com.cloud.storage.Storage.ImageFormat;
import com.cloud.storage.Storage.ProvisioningType;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.OutputInterpreter;
import com.cloud.utils.script.Script;
public class StorPoolStorageAdaptor implements StorageAdaptor {
public static void SP_LOG(String fmt, Object... args) {
try (PrintWriter spLogFile = new PrintWriter(new BufferedWriter(new FileWriter("/var/log/cloudstack/agent/storpool-agent.log", true)))) {
final String line = String.format(fmt, args);
String timeStamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,ms").format(Calendar.getInstance().getTime());
spLogFile.println(timeStamp +" "+line);
spLogFile.flush();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected static Logger LOGGER = LogManager.getLogger(StorPoolStorageAdaptor.class);
private static final Map<String, KVMStoragePool> storageUuidToStoragePool = new HashMap<String, KVMStoragePool>();
@Override
public KVMStoragePool createStoragePool(String uuid, String host, int port, String path, String userInfo, StoragePoolType storagePoolType, Map<String, String> details) {
SP_LOG("StorPoolStorageAdaptor.createStoragePool: uuid=%s, host=%s:%d, path=%s, userInfo=%s, type=%s", uuid, host, port, path, userInfo, storagePoolType);
StorPoolStoragePool storagePool = new StorPoolStoragePool(uuid, host, port, storagePoolType, this);
storageUuidToStoragePool.put(uuid, storagePool);
return storagePool;
}
@Override
public StoragePoolType getStoragePoolType() {
return StoragePoolType.StorPool;
}
@Override
public KVMStoragePool getStoragePool(String uuid) {
SP_LOG("StorPoolStorageAdaptor.getStoragePool: uuid=%s", uuid);
return storageUuidToStoragePool.get(uuid);
}
@Override
public KVMStoragePool getStoragePool(String uuid, boolean refreshInfo) {
SP_LOG("StorPoolStorageAdaptor.getStoragePool: uuid=%s, refresh=%s", uuid, refreshInfo);
return storageUuidToStoragePool.get(uuid);
}
@Override
public boolean deleteStoragePool(String uuid) {
SP_LOG("StorPoolStorageAdaptor.deleteStoragePool: uuid=%s", uuid);
return storageUuidToStoragePool.remove(uuid) != null;
}
@Override
public boolean deleteStoragePool(KVMStoragePool pool) {
SP_LOG("StorPoolStorageAdaptor.deleteStoragePool: uuid=%s", pool.getUuid());
return deleteStoragePool(pool.getUuid());
}
private static long getDeviceSize(final String devPath) {
SP_LOG("StorPoolStorageAdaptor.getDeviceSize: path=%s", devPath);
if (getVolumeNameFromPath(devPath, true) == null) {
return 0;
}
File file = new File(devPath);
if (!file.exists()) {
return 0;
}
Script sc = new Script("blockdev", 0, LOGGER);
sc.add("--getsize64", devPath);
OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
String res = sc.execute(parser);
if (res != null) {
SP_LOG("Unable to retrieve device size for %s. Res: %s", devPath, res);
LOGGER.debug(String.format("Unable to retrieve device size for %s. Res: %s", devPath, res));
return 0;
}
return Long.parseLong(parser.getLine());
}
private static boolean waitForDeviceSymlink(String devPath) {
final int numTries = 10;
final int sleepTime = 100;
for(int i = 0; i < numTries; i++) {
if (getDeviceSize(devPath) != 0) {
return true;
} else {
try {
Thread.sleep(sleepTime);
} catch (Exception ex) {
// don't do anything
}
}
}
return false;
}
public static String getVolumeNameFromPath(final String volumeUuid, boolean tildeNeeded) {
if (volumeUuid.startsWith("/dev/storpool/")) {
return volumeUuid.split("/")[3];
} else if (volumeUuid.startsWith("/dev/storpool-byid/")) {
return tildeNeeded ? "~" + volumeUuid.split("/")[3] : volumeUuid.split("/")[3];
}
return null;
}
public static boolean attachOrDetachVolume(String command, String type, String volumeUuid) {
final String name = getVolumeNameFromPath(volumeUuid, true);
if (name == null) {
return false;
}
SP_LOG("StorPoolStorageAdaptor.attachOrDetachVolume: cmd=%s, type=%s, uuid=%s, name=%s", command, type, volumeUuid, name);
final int numTries = 10;
final int sleepTime = 1000;
String err = null;
for(int i = 0; i < numTries; i++) {
Script sc = new Script("storpool", 0, LOGGER);
sc.add("-M");
sc.add(command);
sc.add(type, name);
sc.add("here");
if (command.equals("attach")) {
sc.add("onRemoteAttached");
sc.add("export");
}
OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
String res = sc.execute(parser);
if (res == null) {
err = null;
break;
}
err = String.format("Unable to %s volume %s. Error: %s", command, name, res);
if (command.equals("detach")) {
try {
Thread.sleep(sleepTime);
} catch (Exception ex) {
// don't do anything
}
} else {
break;
}
}
if (err != null) {
SP_LOG(err);
LOGGER.warn(err);
throw new CloudRuntimeException(err);
}
if (command.equals("attach")) {
return waitForDeviceSymlink(volumeUuid);
} else {
return true;
}
}
public static boolean resize(String newSize, String volumeUuid ) {
final String name = getVolumeNameFromPath(volumeUuid, true);
if (name == null) {
return false;
}
SP_LOG("StorPoolStorageAdaptor.resize: size=%s, uuid=%s, name=%s", newSize, volumeUuid, name);
Script sc = new Script("storpool", 0, LOGGER);
sc.add("-M");
sc.add("volume");
sc.add(name);
sc.add("update");
sc.add("size");
sc.add(newSize);
sc.add("shrinkOk");
OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
String res = sc.execute(parser);
if (res == null) {
return true;
}
String err = String.format("Unable to resize volume %s. Error: %s", name, res);
SP_LOG(err);
LOGGER.warn(err);
throw new CloudRuntimeException(err);
}
@Override
public KVMPhysicalDisk getPhysicalDisk(String volumeUuid, KVMStoragePool pool) {
SP_LOG("StorPoolStorageAdaptor.getPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool);
LOGGER.debug(String.format("getPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool));
final long deviceSize = getDeviceSize(volumeUuid);
KVMPhysicalDisk physicalDisk = new KVMPhysicalDisk(volumeUuid, volumeUuid, pool);
physicalDisk.setFormat(PhysicalDiskFormat.RAW);
physicalDisk.setSize(deviceSize);
physicalDisk.setVirtualSize(deviceSize);
return physicalDisk;
}
@Override
public boolean connectPhysicalDisk(String volumeUuid, KVMStoragePool pool, Map<String, String> details) {
SP_LOG("StorPoolStorageAdaptor.connectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool);
LOGGER.debug(String.format("connectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool));
return attachOrDetachVolume("attach", "volume", volumeUuid);
}
@Override
public boolean disconnectPhysicalDisk(String volumeUuid, KVMStoragePool pool) {
SP_LOG("StorPoolStorageAdaptor.disconnectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool);
LOGGER.debug(String.format("disconnectPhysicalDisk: uuid=%s, pool=%s", volumeUuid, pool));
return attachOrDetachVolume("detach", "volume", volumeUuid);
}
public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect) {
String volumeUuid = volumeToDisconnect.get(DiskTO.UUID);
LOGGER.debug(String.format("StorPoolStorageAdaptor.disconnectPhysicalDisk: map. uuid=%s", volumeUuid));
return attachOrDetachVolume("detach", "volume", volumeUuid);
}
@Override
public boolean disconnectPhysicalDiskByPath(String localPath) {
LOGGER.debug(String.format("disconnectPhysicalDiskByPath: localPath=%s", localPath));
return attachOrDetachVolume("detach", "volume", localPath);
}
@Override
public boolean deletePhysicalDisk(String volumeUuid, KVMStoragePool pool, Storage.ImageFormat format) {
// Should only come here when cleaning-up StorPool snapshots associated with CloudStack templates.
SP_LOG("StorPoolStorageAdaptor.deletePhysicalDisk: uuid=%s, pool=%s, format=%s", volumeUuid, pool, format);
final String name = getVolumeNameFromPath(volumeUuid, true);
if (name == null) {
final String err = String.format("StorPoolStorageAdaptor.deletePhysicalDisk: '%s' is not a StorPool volume?", volumeUuid);
SP_LOG(err);
throw new UnsupportedOperationException(err);
}
Script sc = new Script("storpool", 0, LOGGER);
sc.add("-M");
sc.add("snapshot", name);
sc.add("delete", name);
OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser();
String res = sc.execute(parser);
if (res != null) {
final String err = String.format("Unable to delete StorPool snapshot '%s'. Error: %s", name, res);
SP_LOG(err);
LOGGER.warn(err);
throw new UnsupportedOperationException(err);
}
return true; // apparently ignored
}
@Override
public List<KVMPhysicalDisk> listPhysicalDisks(String storagePoolUuid, KVMStoragePool pool) {
SP_LOG("StorPoolStorageAdaptor.listPhysicalDisks: uuid=%s, pool=%s", storagePoolUuid, pool);
throw new UnsupportedOperationException("Listing disks is not supported for this configuration.");
}
@Override
public KVMPhysicalDisk createTemplateFromDisk(KVMPhysicalDisk disk, String name, PhysicalDiskFormat format, long size, KVMStoragePool destPool) {
SP_LOG("StorPoolStorageAdaptor.createTemplateFromDisk: disk=%s, name=%s, fmt=%s, size=%d, dst_pool=%s", disk, name, format, size, destPool.getUuid());
throw new UnsupportedOperationException("Creating a template from a disk is not yet supported for this configuration.");
}
@Override
public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout, byte[] sourcePassphrase, byte[] destPassphrase, ProvisioningType provisioningType) {
return copyPhysicalDisk(disk, name, destPool, timeout);
}
@Override
public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPool, int timeout) {
SP_LOG("StorPoolStorageAdaptor.copyPhysicalDisk: disk=%s, name=%s, dst_pool=%s, to=%d", disk, name, destPool.getUuid(), timeout);
throw new UnsupportedOperationException("Copying a disk is not supported in this configuration.");
}
public KVMPhysicalDisk createDiskFromSnapshot(KVMPhysicalDisk snapshot, String snapshotName, String name, KVMStoragePool destPool) {
SP_LOG("StorPoolStorageAdaptor.createDiskFromSnapshot: snap=%s, snap_name=%s, name=%s, dst_pool=%s", snapshot, snapshotName, name, destPool.getUuid());
throw new UnsupportedOperationException("Creating a disk from a snapshot is not supported in this configuration.");
}
@Override
public boolean refresh(KVMStoragePool pool) {
SP_LOG("StorPoolStorageAdaptor.refresh: pool=%s", pool);
return true;
}
@Override
public boolean createFolder(String uuid, String path) {
SP_LOG("StorPoolStorageAdaptor.createFolder: uuid=%s, path=%s", uuid, path);
throw new UnsupportedOperationException("A folder cannot be created in this configuration.");
}
public KVMPhysicalDisk createTemplateFromDirectDownloadFile(String templateFilePath, String destTemplatePath,
KVMStoragePool destPool, ImageFormat format, int timeout) {
return null;
}
@Override
public boolean createFolder(String uuid, String path, String localPath) {
return false;
}
@Override
public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, PhysicalDiskFormat format,
ProvisioningType provisioningType, long size, byte[] passphrase) {
return null;
}
@Override
public KVMPhysicalDisk createDiskFromTemplate(KVMPhysicalDisk template, String name, PhysicalDiskFormat format,
ProvisioningType provisioningType, long size, KVMStoragePool destPool, int timeout, byte[] passphrase) {
return null;
}
@Override
public KVMPhysicalDisk createDiskFromTemplateBacking(KVMPhysicalDisk template, String name,
PhysicalDiskFormat format, long size, KVMStoragePool destPool, int timeout, byte[] passphrase) {
return null;
}
}