Skip to content

Commit df99a29

Browse files
authored
linstor: Fix using multiple primary storage with same linstor-controller (#10280)
1 parent 90c960e commit df99a29

File tree

4 files changed

+458
-49
lines changed

4 files changed

+458
-49
lines changed

plugins/storage/volume/linstor/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to Linstor CloudStack plugin will be documented in this file
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2025-01-27]
9+
10+
### Fixed
11+
12+
- Use of multiple primary storages on the same linstor controller
13+
814
## [2025-01-20]
915

1016
### Fixed

plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

Lines changed: 108 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@
6060
import com.linbit.linstor.api.model.VolumeDefinition;
6161

6262
import java.io.File;
63+
import java.io.FileInputStream;
64+
import java.io.IOException;
65+
import java.nio.file.Files;
66+
import java.nio.file.Path;
67+
import java.nio.file.Paths;
6368

6469
@StorageAdaptorInfo(storagePoolType=Storage.StoragePoolType.Linstor)
6570
public class LinstorStorageAdaptor implements StorageAdaptor {
@@ -198,10 +203,10 @@ public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, Qemu
198203
final DevelopersApi api = getLinstorAPI(pool);
199204

200205
try {
201-
List<ResourceDefinition> definitionList = api.resourceDefinitionList(
202-
Collections.singletonList(rscName), null, null, null);
206+
ResourceDefinition resourceDefinition = LinstorUtil.findResourceDefinition(
207+
api, rscName, lpool.getResourceGroup());
203208

204-
if (definitionList.isEmpty()) {
209+
if (resourceDefinition == null) {
205210
ResourceGroupSpawn rgSpawn = new ResourceGroupSpawn();
206211
rgSpawn.setResourceDefinitionName(rscName);
207212
rgSpawn.addVolumeSizesItem(size / 1024); // linstor uses KiB
@@ -211,22 +216,28 @@ public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, Qemu
211216
handleLinstorApiAnswers(answers, "Linstor: Unable to spawn resource.");
212217
}
213218

219+
String foundRscName = resourceDefinition != null ? resourceDefinition.getName() : rscName;
220+
214221
// query linstor for the device path
215222
List<ResourceWithVolumes> resources = api.viewResources(
216223
Collections.emptyList(),
217-
Collections.singletonList(rscName),
224+
Collections.singletonList(foundRscName),
218225
Collections.emptyList(),
219226
null,
220227
null,
221228
null);
222229

223-
makeResourceAvailable(api, rscName, false);
230+
makeResourceAvailable(api, foundRscName, false);
224231

225232
if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty()) {
226233
final String devPath = resources.get(0).getVolumes().get(0).getDevicePath();
227234
s_logger.info("Linstor: Created drbd device: " + devPath);
228235
final KVMPhysicalDisk kvmDisk = new KVMPhysicalDisk(devPath, name, pool);
229236
kvmDisk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
237+
long allocatedKib = resources.get(0).getVolumes().get(0).getAllocatedSizeKib() != null ?
238+
resources.get(0).getVolumes().get(0).getAllocatedSizeKib() : 0;
239+
kvmDisk.setSize(allocatedKib >= 0 ? allocatedKib * 1024 : 0);
240+
kvmDisk.setVirtualSize(size);
230241
return kvmDisk;
231242
} else {
232243
s_logger.error("Linstor: viewResources didn't return resources or volumes.");
@@ -470,21 +481,56 @@ public boolean disconnectPhysicalDiskByPath(String localPath)
470481
return false;
471482
}
472483

484+
/**
485+
* Decrements the aux property key for template resource and deletes or just deletes if not template resource.
486+
* @param api
487+
* @param rscName
488+
* @param rscGrpName
489+
* @return
490+
* @throws ApiException
491+
*/
492+
private boolean deRefOrDeleteResource(DevelopersApi api, String rscName, String rscGrpName) throws ApiException {
493+
boolean deleted = false;
494+
List<ResourceDefinition> existingRDs = LinstorUtil.getRDListStartingWith(api, rscName);
495+
for (ResourceDefinition rd : existingRDs) {
496+
int expectedProps = 0; // if it is a non template resource, we don't expect any _cs-template-for- prop
497+
String propKey = LinstorUtil.getTemplateForAuxPropKey(rscGrpName);
498+
if (rd.getProps().containsKey(propKey)) {
499+
ResourceDefinitionModify rdm = new ResourceDefinitionModify();
500+
rdm.deleteProps(Collections.singletonList(propKey));
501+
api.resourceDefinitionModify(rd.getName(), rdm);
502+
expectedProps = 1;
503+
}
504+
505+
// if there is only one template-for property left for templates, the template isn't needed anymore
506+
// or if it isn't a template anyway, it will not have this Aux property
507+
// _cs-template-for- poperties work like a ref-count.
508+
if (rd.getProps().keySet().stream()
509+
.filter(key -> key.startsWith("Aux/" + LinstorUtil.CS_TEMPLATE_FOR_PREFIX))
510+
.count() == expectedProps) {
511+
ApiCallRcList answers = api.resourceDefinitionDelete(rd.getName());
512+
checkLinstorAnswersThrow(answers);
513+
deleted = true;
514+
}
515+
}
516+
return deleted;
517+
}
518+
473519
@Override
474520
public boolean deletePhysicalDisk(String name, KVMStoragePool pool, Storage.ImageFormat format)
475521
{
476522
s_logger.debug("Linstor: deletePhysicalDisk " + name);
477523
final DevelopersApi api = getLinstorAPI(pool);
524+
final String rscName = getLinstorRscName(name);
525+
final LinstorStoragePool linstorPool = (LinstorStoragePool) pool;
526+
String rscGrpName = linstorPool.getResourceGroup();
478527

479528
try {
480-
final String rscName = getLinstorRscName(name);
481-
s_logger.debug("Linstor: delete resource definition " + rscName);
482-
ApiCallRcList answers = api.resourceDefinitionDelete(rscName);
483-
handleLinstorApiAnswers(answers, "Linstor: Unable to delete resource definition " + rscName);
529+
return deRefOrDeleteResource(api, rscName, rscGrpName);
484530
} catch (ApiException apiEx) {
531+
s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
485532
throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
486533
}
487-
return true;
488534
}
489535

490536
@Override
@@ -558,6 +604,56 @@ private boolean resourceSupportZeroBlocks(KVMStoragePool destPool, String resNam
558604
return false;
559605
}
560606

607+
/**
608+
* Checks if the given disk is the SystemVM template, by checking its properties file in the same directory.
609+
* The initial systemvm template resource isn't created on the management server, but
610+
* we now need to know if the systemvm template is used, while copying.
611+
* @param disk
612+
* @return True if it is the systemvm template disk, else false.
613+
*/
614+
private static boolean isSystemTemplate(KVMPhysicalDisk disk) {
615+
Path diskPath = Paths.get(disk.getPath());
616+
Path propFile = diskPath.getParent().resolve("template.properties");
617+
if (Files.exists(propFile)) {
618+
java.util.Properties templateProps = new java.util.Properties();
619+
try {
620+
templateProps.load(new FileInputStream(propFile.toFile()));
621+
String desc = templateProps.getProperty("description");
622+
if (desc.startsWith("SystemVM Template")) {
623+
return true;
624+
}
625+
} catch (IOException e) {
626+
return false;
627+
}
628+
}
629+
return false;
630+
}
631+
632+
/**
633+
* Conditionally sets the correct aux properties for templates or basic resources.
634+
* @param api
635+
* @param srcDisk
636+
* @param destPool
637+
* @param name
638+
*/
639+
private void setRscDfnAuxProperties(
640+
DevelopersApi api, KVMPhysicalDisk srcDisk, KVMStoragePool destPool, String name) {
641+
// if it is the initial systemvm disk copy, we need to apply the _cs-template-for property.
642+
if (isSystemTemplate(srcDisk)) {
643+
applyAuxProps(api, name, "SystemVM Template", null);
644+
LinstorStoragePool linPool = (LinstorStoragePool) destPool;
645+
final String rscName = getLinstorRscName(name);
646+
try {
647+
LinstorUtil.setAuxTemplateForProperty(api, rscName, linPool.getResourceGroup());
648+
} catch (ApiException apiExc) {
649+
s_logger.error(String.format("Error setting aux template for property for %s", rscName));
650+
logLinstorAnswers(apiExc.getApiCallRcList());
651+
}
652+
} else {
653+
applyAuxProps(api, name, srcDisk.getDispName(), srcDisk.getVmName());
654+
}
655+
}
656+
561657
@Override
562658
public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPools, int timeout, byte[] srcPassphrase, byte[] destPassphrase, Storage.ProvisioningType provisioningType)
563659
{
@@ -571,15 +667,14 @@ public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMSt
571667
name, QemuImg.PhysicalDiskFormat.RAW, provisioningType, disk.getVirtualSize(), null);
572668

573669
final DevelopersApi api = getLinstorAPI(destPools);
574-
applyAuxProps(api, name, disk.getDispName(), disk.getVmName());
670+
setRscDfnAuxProperties(api, disk, destPools, name);
575671

576672
s_logger.debug(String.format("Linstor.copyPhysicalDisk: dstPath: %s", dstDisk.getPath()));
577673
final QemuImgFile destFile = new QemuImgFile(dstDisk.getPath());
578674
destFile.setFormat(dstDisk.getFormat());
579675
destFile.setSize(disk.getVirtualSize());
580676

581-
boolean zeroedDevice = resourceSupportZeroBlocks(destPools, LinstorUtil.RSC_PREFIX + name);
582-
677+
boolean zeroedDevice = resourceSupportZeroBlocks(destPools, getLinstorRscName(name));
583678
try {
584679
final QemuImg qemu = new QemuImg(timeout, zeroedDevice, true);
585680
qemu.convert(srcFile, destFile);

0 commit comments

Comments
 (0)