Skip to content

Commit

Permalink
linstor: create template cache image before host copy
Browse files Browse the repository at this point in the history
Create the template image resource before the host gets the copy command.
This allowes to tag(set property) in linstor to which template this
resource belongs to and we can maybe in future reduce the linstor
managing code in the storagepoolAdaptor class.
  • Loading branch information
rp- committed Nov 7, 2023
1 parent a5dbbd7 commit 4e11e95
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.linbit.linstor.api.model.ResourceDefinitionCreate;
import com.linbit.linstor.api.model.ResourceDefinitionModify;
import com.linbit.linstor.api.model.ResourceGroupSpawn;
import com.linbit.linstor.api.model.ResourceMakeAvailable;
import com.linbit.linstor.api.model.ResourceWithVolumes;
import com.linbit.linstor.api.model.Snapshot;
import com.linbit.linstor.api.model.SnapshotRestore;
Expand Down Expand Up @@ -62,6 +63,7 @@
import com.cloud.storage.ResizeVolumePayload;
import com.cloud.storage.SnapshotVO;
import com.cloud.storage.Storage.StoragePoolType;
import com.cloud.storage.Storage;
import com.cloud.storage.StorageManager;
import com.cloud.storage.StoragePool;
import com.cloud.storage.VMTemplateStoragePoolVO;
Expand Down Expand Up @@ -422,27 +424,46 @@ private void applyAuxProps(DevelopersApi api, String rscName, String dispName, S
}
}

private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO)
{
DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
private String getRscGrp(StoragePoolVO storagePoolVO) {
return storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
storagePoolVO.getUserInfo() : "DfltRscGrp";
}

private String createResourceBase(
String rscName, long sizeInBytes, String volName, String vmName, DevelopersApi api, String rscGrp) {
ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn();
final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
rscGrpSpawn.setResourceDefinitionName(rscName);
rscGrpSpawn.addVolumeSizesItem(vol.getSize() / 1024);
rscGrpSpawn.addVolumeSizesItem(sizeInBytes / 1024);

try
{
s_logger.info("Linstor: Spawn resource " + rscName);
ApiCallRcList answers = linstorApi.resourceGroupSpawn(rscGrp, rscGrpSpawn);
ApiCallRcList answers = api.resourceGroupSpawn(rscGrp, rscGrpSpawn);
checkLinstorAnswersThrow(answers);

applyAuxProps(linstorApi, rscName, vol.getName(), vol.getAttachedVmName());
applyAuxProps(api, rscName, volName, vmName);

return getDeviceName(api, rscName);
} catch (ApiException apiEx)
{
s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
}
}

private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO) {
DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
final String rscGrp = getRscGrp(storagePoolVO);

final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
String deviceName = createResourceBase(
rscName, vol.getSize(), vol.getName(), vol.getAttachedVmName(), linstorApi, rscGrp);

try
{
applyQoSSettings(storagePoolVO, linstorApi, rscName, vol.getMaxIops());

return getDeviceName(linstorApi, rscName);
return deviceName;
} catch (ApiException apiEx)
{
s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
Expand Down Expand Up @@ -507,8 +528,7 @@ private String cloneResource(long csCloneId, VolumeInfo volumeInfo, StoragePoolV
}

private String createResourceFromSnapshot(long csSnapshotId, String rscName, StoragePoolVO storagePoolVO) {
final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
storagePoolVO.getUserInfo() : "DfltRscGrp";
final String rscGrp = getRscGrp(storagePoolVO);
final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());

SnapshotVO snapshotVO = _snapshotDao.findById(csSnapshotId);
Expand Down Expand Up @@ -759,6 +779,13 @@ private static boolean canCopySnapshotCond(DataObject srcData, DataObject dstDat
|| dstData.getDataStore().getRole() == DataStoreRole.ImageCache);
}

private static boolean canCopyTemplateCond(DataObject srcData, DataObject dstData) {
return srcData.getType() == DataObjectType.TEMPLATE && dstData.getType() == DataObjectType.TEMPLATE
&& dstData.getDataStore().getRole() == DataStoreRole.Primary
&& (srcData.getDataStore().getRole() == DataStoreRole.Image
|| srcData.getDataStore().getRole() == DataStoreRole.ImageCache);
}

@Override
public boolean canCopy(DataObject srcData, DataObject dstData)
{
Expand All @@ -769,6 +796,12 @@ public boolean canCopy(DataObject srcData, DataObject dstData)
VolumeInfo volume = sinfo.getBaseVolume();
StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId());
return storagePool.getStorageProviderName().equals(LinstorUtil.PROVIDER_NAME);
} else if (canCopyTemplateCond(srcData, dstData)) {
TemplateInfo tInfo = (TemplateInfo) dstData;
StoragePoolVO storagePoolVO = _storagePoolDao.findById(dstData.getDataStore().getId());
return storagePoolVO != null
&& storagePoolVO.getPoolType() == Storage.StoragePoolType.Linstor
&& tInfo.getSize() != null;
}
return false;
}
Expand Down Expand Up @@ -796,6 +829,9 @@ public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCal
}
res = new CopyCommandResult(null, answer);
res.setResult(errMsg);
} else if (canCopyTemplateCond(srcData, dstData)) {
Answer answer = copyTemplate(srcData, dstData);
res = new CopyCommandResult(null, answer);
} else {
Answer answer = new Answer(null, false, "noimpl");
res = new CopyCommandResult(null, answer);
Expand All @@ -804,6 +840,39 @@ public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCal
callback.complete(res);
}

private Optional<RemoteHostEndPoint> getLinstorEP(DevelopersApi api, String rscName) throws ApiException {
List<String> linstorNodeNames = LinstorUtil.getLinstorNodeNames(api);
Collections.shuffle(linstorNodeNames); // do not always pick the first linstor node

Host host = null;
for (String nodeName : linstorNodeNames) {
host = _hostDao.findByName(nodeName);
if (host != null) {
s_logger.info(String.format("Linstor: Make resource %s available on node %s ...", rscName, nodeName));
ApiCallRcList answers = api.resourceMakeAvailableOnNode(rscName, nodeName, new ResourceMakeAvailable());
if (!answers.hasError()) {
break; // found working host
} else {
s_logger.error(
String.format("Linstor: Unable to make resource %s on node %s available: %s",
rscName,
nodeName,
LinstorUtil.getBestErrorMessage(answers)));
}
}
}

if (host == null)
{
s_logger.error("Linstor: Couldn't create a resource on any cloudstack host.");
return Optional.empty();
}
else
{
return Optional.of(RemoteHostEndPoint.getHypervisorHostEndPoint(host));
}
}

private Optional<RemoteHostEndPoint> getDiskfullEP(DevelopersApi api, String rscName) throws ApiException {
com.linbit.linstor.api.model.StoragePool linSP =
LinstorUtil.getDiskfulStoragePool(api, rscName);
Expand All @@ -823,6 +892,43 @@ private Optional<RemoteHostEndPoint> getDiskfullEP(DevelopersApi api, String rsc
return Optional.empty();
}

private Answer copyTemplate(DataObject srcData, DataObject dstData) {
TemplateInfo tInfo = (TemplateInfo) dstData;
final StoragePoolVO pool = _storagePoolDao.findById(dstData.getDataStore().getId());
final DevelopersApi api = LinstorUtil.getLinstorAPI(pool.getHostAddress());
final String rscName = LinstorUtil.RSC_PREFIX + dstData.getUuid();
createResourceBase(
LinstorUtil.RSC_PREFIX + dstData.getUuid(),
tInfo.getSize(),
tInfo.getName(),
"",
api,
getRscGrp(pool));

int nMaxExecutionMinutes = NumbersUtil.parseInt(
_configDao.getValue(Config.SecStorageCmdExecutionTimeMax.key()), 30);
CopyCommand cmd = new CopyCommand(
srcData.getTO(),
dstData.getTO(),
nMaxExecutionMinutes * 60 * 1000,
VirtualMachineManager.ExecuteInSequence.value());
Answer answer;

try {
Optional<RemoteHostEndPoint> optEP = getLinstorEP(api, rscName);
if (optEP.isPresent()) {
answer = optEP.get().sendMessage(cmd);
}
else {
answer = new Answer(cmd, false, "Unable to get matching Linstor endpoint.");
}
} catch (ApiException exc) {
s_logger.error("copy template failed: ", exc);
throw new CloudRuntimeException(exc.getBestMessage());
}
return answer;
}

protected Answer copySnapshot(DataObject srcData, DataObject destData) {
String value = _configDao.getValue(Config.BackupSnapshotWait.toString());
int _backupsnapshotwait = NumbersUtil.parseInt(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.linbit.linstor.api.DevelopersApi;
import com.linbit.linstor.api.model.ApiCallRc;
import com.linbit.linstor.api.model.ApiCallRcList;
import com.linbit.linstor.api.model.Node;
import com.linbit.linstor.api.model.ProviderKind;
import com.linbit.linstor.api.model.ResourceGroup;
import com.linbit.linstor.api.model.ResourceWithVolumes;
Expand All @@ -32,6 +33,7 @@

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.log4j.Logger;
Expand Down Expand Up @@ -63,6 +65,18 @@ public static String getBestErrorMessage(ApiCallRcList answers) {
.orElse((answers.get(0)).getMessage()) : null;
}

public static List<String> getLinstorNodeNames(@Nonnull DevelopersApi api) throws ApiException
{
List<Node> nodes = api.nodeList(
Collections.emptyList(),
Collections.emptyList(),
null,
null
);

return nodes.stream().map(Node::getName).collect(Collectors.toList());
}

public static com.linbit.linstor.api.model.StoragePool
getDiskfulStoragePool(@Nonnull DevelopersApi api, @Nonnull String rscName) throws ApiException
{
Expand Down

0 comments on commit 4e11e95

Please sign in to comment.