Skip to content

Commit

Permalink
linstor: Fix using multiple primary storage with same linstor-controller
Browse files Browse the repository at this point in the history
It should have been always possible to use multiple primary storages,
with the same linstor-controller, by just using different resource-groups
with different settings.
But if the same template was used on 2 different primary storages,
there would be a name collision on the linstor-controller, as 2 of them
would get allocated to the same name.
This commit fixes this, by intelligently reusing the same template,
as long as possible (if select filter properties match enough).
  • Loading branch information
rp- committed Feb 5, 2025
1 parent 90c960e commit fa245cc
Show file tree
Hide file tree
Showing 4 changed files with 458 additions and 49 deletions.
6 changes: 6 additions & 0 deletions plugins/storage/volume/linstor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to Linstor CloudStack plugin will be documented in this file
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2025-01-27]

### Fixed

- Use of multiple primary storages on the same linstor controller

## [2025-01-20]

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@
import com.linbit.linstor.api.model.VolumeDefinition;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

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

try {
List<ResourceDefinition> definitionList = api.resourceDefinitionList(
Collections.singletonList(rscName), null, null, null);
ResourceDefinition resourceDefinition = LinstorUtil.findResourceDefinition(
api, rscName, lpool.getResourceGroup());

Check warning on line 207 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java#L206-L207

Added lines #L206 - L207 were not covered by tests

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

String foundRscName = resourceDefinition != null ? resourceDefinition.getName() : rscName;

// query linstor for the device path
List<ResourceWithVolumes> resources = api.viewResources(
Collections.emptyList(),
Collections.singletonList(rscName),
Collections.singletonList(foundRscName),

Check warning on line 224 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L224 was not covered by tests
Collections.emptyList(),
null,
null,
null);

makeResourceAvailable(api, rscName, false);
makeResourceAvailable(api, foundRscName, false);

Check warning on line 230 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L230 was not covered by tests

if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty()) {
final String devPath = resources.get(0).getVolumes().get(0).getDevicePath();
s_logger.info("Linstor: Created drbd device: " + devPath);
final KVMPhysicalDisk kvmDisk = new KVMPhysicalDisk(devPath, name, pool);
kvmDisk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
long allocatedKib = resources.get(0).getVolumes().get(0).getAllocatedSizeKib() != null ?
resources.get(0).getVolumes().get(0).getAllocatedSizeKib() : 0;

Check warning on line 238 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L238 was not covered by tests
kvmDisk.setSize(allocatedKib >= 0 ? allocatedKib * 1024 : 0);
kvmDisk.setVirtualSize(size);

Check warning on line 240 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L240 was not covered by tests
return kvmDisk;
} else {
s_logger.error("Linstor: viewResources didn't return resources or volumes.");
Expand Down Expand Up @@ -470,21 +481,56 @@ public boolean disconnectPhysicalDiskByPath(String localPath)
return false;
}

/**
* Decrements the aux property key for template resource and deletes or just deletes if not template resource.
* @param api
* @param rscName
* @param rscGrpName
* @return
* @throws ApiException
*/
private boolean deRefOrDeleteResource(DevelopersApi api, String rscName, String rscGrpName) throws ApiException {
boolean deleted = false;
List<ResourceDefinition> existingRDs = LinstorUtil.getRDListStartingWith(api, rscName);

Check warning on line 494 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java#L492-L494

Added lines #L492 - L494 were not covered by tests
for (ResourceDefinition rd : existingRDs) {
int expectedProps = 0; // if it is a non template resource, we don't expect any _cs-template-for- prop
String propKey = LinstorUtil.getTemplateForAuxPropKey(rscGrpName);

Check warning on line 497 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java#L496-L497

Added lines #L496 - L497 were not covered by tests
if (rd.getProps().containsKey(propKey)) {
ResourceDefinitionModify rdm = new ResourceDefinitionModify();
rdm.deleteProps(Collections.singletonList(propKey));
api.resourceDefinitionModify(rd.getName(), rdm);
expectedProps = 1;

Check warning on line 502 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java#L499-L502

Added lines #L499 - L502 were not covered by tests
}

// if there is only one template-for property left for templates, the template isn't needed anymore
// or if it isn't a template anyway, it will not have this Aux property
// _cs-template-for- poperties work like a ref-count.
if (rd.getProps().keySet().stream()
.filter(key -> key.startsWith("Aux/" + LinstorUtil.CS_TEMPLATE_FOR_PREFIX))

Check warning on line 509 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java#L508-L509

Added lines #L508 - L509 were not covered by tests
.count() == expectedProps) {
ApiCallRcList answers = api.resourceDefinitionDelete(rd.getName());
checkLinstorAnswersThrow(answers);
deleted = true;

Check warning on line 513 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java#L511-L513

Added lines #L511 - L513 were not covered by tests
}
}
return deleted;
}

Check warning on line 517 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java#L515-L517

Added lines #L515 - L517 were not covered by tests

@Override
public boolean deletePhysicalDisk(String name, KVMStoragePool pool, Storage.ImageFormat format)
{
s_logger.debug("Linstor: deletePhysicalDisk " + name);
final DevelopersApi api = getLinstorAPI(pool);
final String rscName = getLinstorRscName(name);
final LinstorStoragePool linstorPool = (LinstorStoragePool) pool;
String rscGrpName = linstorPool.getResourceGroup();

Check warning on line 526 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java#L524-L526

Added lines #L524 - L526 were not covered by tests

try {
final String rscName = getLinstorRscName(name);
s_logger.debug("Linstor: delete resource definition " + rscName);
ApiCallRcList answers = api.resourceDefinitionDelete(rscName);
handleLinstorApiAnswers(answers, "Linstor: Unable to delete resource definition " + rscName);
return deRefOrDeleteResource(api, rscName, rscGrpName);

Check warning on line 529 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L529 was not covered by tests
} catch (ApiException apiEx) {
s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());

Check warning on line 531 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L531 was not covered by tests
throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
}
return true;
}

@Override
Expand Down Expand Up @@ -558,6 +604,56 @@ private boolean resourceSupportZeroBlocks(KVMStoragePool destPool, String resNam
return false;
}

/**
* Checks if the given disk is the SystemVM template, by checking its properties file in the same directory.
* The initial systemvm template resource isn't created on the management server, but
* we now need to know if the systemvm template is used, while copying.
* @param disk
* @return True if it is the systemvm template disk, else false.
*/
private static boolean isSystemTemplate(KVMPhysicalDisk disk) {
Path diskPath = Paths.get(disk.getPath());
Path propFile = diskPath.getParent().resolve("template.properties");

Check warning on line 616 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java#L614-L616

Added lines #L614 - L616 were not covered by tests
if (Files.exists(propFile)) {
java.util.Properties templateProps = new java.util.Properties();
try {
templateProps.load(new FileInputStream(propFile.toFile()));
String desc = templateProps.getProperty("description");

Check warning on line 621 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java#L618-L621

Added lines #L618 - L621 were not covered by tests
if (desc.startsWith("SystemVM Template")) {
return true;

Check warning on line 623 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L623 was not covered by tests
}
} catch (IOException e) {
return false;
}

Check warning on line 627 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java#L625-L627

Added lines #L625 - L627 were not covered by tests
}
return false;
}

Check warning on line 630 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java#L629-L630

Added lines #L629 - L630 were not covered by tests

/**
* Conditionally sets the correct aux properties for templates or basic resources.
* @param api
* @param srcDisk
* @param destPool
* @param name
*/
private void setRscDfnAuxProperties(
DevelopersApi api, KVMPhysicalDisk srcDisk, KVMStoragePool destPool, String name) {

Check warning on line 640 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L640 was not covered by tests
// if it is the initial systemvm disk copy, we need to apply the _cs-template-for property.
if (isSystemTemplate(srcDisk)) {
applyAuxProps(api, name, "SystemVM Template", null);
LinstorStoragePool linPool = (LinstorStoragePool) destPool;
final String rscName = getLinstorRscName(name);
try {
LinstorUtil.setAuxTemplateForProperty(api, rscName, linPool.getResourceGroup());
} catch (ApiException apiExc) {
s_logger.error(String.format("Error setting aux template for property for %s", rscName));
logLinstorAnswers(apiExc.getApiCallRcList());
}
} else {
applyAuxProps(api, name, srcDisk.getDispName(), srcDisk.getVmName());

Check warning on line 653 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java#L643-L653

Added lines #L643 - L653 were not covered by tests
}
}

Check warning on line 655 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L655 was not covered by tests

@Override
public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPools, int timeout, byte[] srcPassphrase, byte[] destPassphrase, Storage.ProvisioningType provisioningType)
{
Expand All @@ -571,15 +667,14 @@ public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMSt
name, QemuImg.PhysicalDiskFormat.RAW, provisioningType, disk.getVirtualSize(), null);

final DevelopersApi api = getLinstorAPI(destPools);
applyAuxProps(api, name, disk.getDispName(), disk.getVmName());
setRscDfnAuxProperties(api, disk, destPools, name);

Check warning on line 670 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L670 was not covered by tests

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

boolean zeroedDevice = resourceSupportZeroBlocks(destPools, LinstorUtil.RSC_PREFIX + name);

boolean zeroedDevice = resourceSupportZeroBlocks(destPools, getLinstorRscName(name));

Check warning on line 677 in plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L677 was not covered by tests
try {
final QemuImg qemu = new QemuImg(timeout, zeroedDevice, true);
qemu.convert(srcFile, destFile);
Expand Down
Loading

0 comments on commit fa245cc

Please sign in to comment.