This is an automated email from the ASF dual-hosted git repository.

rp- pushed a commit to branch 4.22
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.22 by this push:
     new 0231e67b9d9 Linstor: Add controller token auth support (#13470)
0231e67b9d9 is described below

commit 0231e67b9d9adf1ac0b4a4bd7a9acbf654e09cbc
Author: Rene Peinthor <[email protected]>
AuthorDate: Mon Jun 29 20:15:56 2026 +0200

    Linstor: Add controller token auth support (#13470)
    
    * linstor: update java-linstor to 0.7.0 to support auth token api
    
    With Linstor 1.34.0 a new authentication mode is supported:
    * Bearer token
    
    To support that it had to be implemented in the java-linstor library
    and we need to store the auth token per storage pool.
    Also per default with this auth mode Linstor will run with
    HTTPS enabled, so we also have to support that.
    
    * ui: seed default-on toggles in zone wizard while hidden
    
    StaticInputsForm.fillValue() only seeded defaults for currently-displayed
    fields, so a display-gated switch with checked:true bound to an undefined
    value and rendered as off once revealed. Seed checked switch/checkbox
    fields even while hidden, so the Linstor 'Allow self-signed certificate'
    toggle defaults on in the zone creation wizard.
---
 plugins/storage/volume/linstor/CHANGELOG.md        |   6 ++
 .../kvm/storage/LinstorStorageAdaptor.java         |  27 +++++-
 .../hypervisor/kvm/storage/LinstorStoragePool.java |   8 +-
 .../driver/LinstorPrimaryDataStoreDriverImpl.java  |  41 +++++---
 .../LinstorPrimaryDataStoreLifeCycleImpl.java      |  40 +++++++-
 .../util/LinstorConfigChangeListener.java          | 108 +++++++++++++++++++++
 .../util/LinstorConfigurationManager.java          |   9 +-
 .../storage/datastore/util/LinstorUtil.java        |  33 +++++--
 .../storage/motion/LinstorDataMotionStrategy.java  |   9 +-
 .../snapshot/LinstorVMSnapshotStrategy.java        |  19 +++-
 .../spring-storage-volume-linstor-context.xml      |   2 +
 pom.xml                                            |   2 +-
 .../plugins/linstor/test_linstor_volumes.py        |  12 +++
 ui/public/locales/en.json                          |   4 +
 ui/src/views/infra/AddPrimaryStorage.vue           |  19 +++-
 ui/src/views/infra/zone/StaticInputsForm.vue       |   5 +
 ui/src/views/infra/zone/ZoneWizardAddResources.vue |  24 ++++-
 ui/src/views/infra/zone/ZoneWizardLaunchZone.vue   |   4 +
 18 files changed, 333 insertions(+), 39 deletions(-)

diff --git a/plugins/storage/volume/linstor/CHANGELOG.md 
b/plugins/storage/volume/linstor/CHANGELOG.md
index 1a3142e8c59..070a752db04 100644
--- a/plugins/storage/volume/linstor/CHANGELOG.md
+++ b/plugins/storage/volume/linstor/CHANGELOG.md
@@ -24,6 +24,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).
 
+## [2026-06-03]
+
+### Added
+
+- Support Linstor bearer token authentication (Linstor 1.34.0) and HTTPS 
controller connections
+
 ## [2026-01-17]
 
 ### Added
diff --git 
a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
 
b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
index 77953a32e63..848bdc60d66 100644
--- 
a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
+++ 
b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
@@ -30,6 +30,7 @@ import javax.annotation.Nonnull;
 import com.cloud.storage.Storage;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.script.Script;
+import 
org.apache.cloudstack.storage.datastore.util.LinstorConfigurationManager;
 import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
 import org.apache.cloudstack.utils.qemu.QemuImg;
 import org.apache.cloudstack.utils.qemu.QemuImgException;
@@ -42,7 +43,6 @@ import com.cloud.utils.storage.TemplateDownloaderUtil;
 import com.linbit.linstor.api.ApiClient;
 import com.linbit.linstor.api.ApiConsts;
 import com.linbit.linstor.api.ApiException;
-import com.linbit.linstor.api.Configuration;
 import com.linbit.linstor.api.DevelopersApi;
 import com.linbit.linstor.api.model.ApiCallRc;
 import com.linbit.linstor.api.model.ApiCallRcList;
@@ -72,8 +72,17 @@ public class LinstorStorageAdaptor implements StorageAdaptor 
{
     private final String localNodeName;
 
     private DevelopersApi getLinstorAPI(KVMStoragePool pool) {
-        ApiClient client = Configuration.getDefaultApiClient();
+        // Use a fresh client per pool so a self-signed/insecure pool can't 
weaken the TLS settings
+        // of another pool sharing this agent.
+        ApiClient client = new ApiClient();
         client.setBasePath(pool.getSourceHost());
+        // The agent has no access to the per-pool API token config; fall back 
to the auth.json file
+        // on the host (/var/lib/linstor.d/auth.json), or stay unauthenticated 
if it is absent.
+        client.setAccessTokenWithFallback(null);
+        if (pool instanceof LinstorStoragePool && ((LinstorStoragePool) 
pool).isInsecureSsl()) {
+            client.setInsecureSsl();
+        }
+        client.discoverHttps();
         return new DevelopersApi(client);
     }
 
@@ -166,7 +175,16 @@ public class LinstorStorageAdaptor implements 
StorageAdaptor {
                                             Storage.StoragePoolType type, 
Map<String, String> details, boolean isPrimaryStorage)
     {
         logger.debug("Linstor createStoragePool: name: '{}', host: '{}', path: 
{}, userinfo: {}", name, host, path, userInfo);
-        LinstorStoragePool storagePool = new LinstorStoragePool(name, host, 
port, userInfo, type, this);
+        // The management server ships the per-pool config in the details map; 
the controller TLS
+        // verification can be disabled here for self-signed certificates. The 
ConfigKey default is only
+        // applied on the management server (via valueIn()) and is NOT shipped 
in the details, so when the
+        // detail is absent we must fall back to that default here too - 
otherwise a pool without an
+        // explicit setting would verify the certificate on the agent while 
the MS thinks it is disabled.
+        final String insecureSslDetail = details != null ? 
details.get(LinstorConfigurationManager.InsecureSsl.key()) : null;
+        boolean insecureSsl = insecureSslDetail != null
+                ? Boolean.parseBoolean(insecureSslDetail)
+                : 
Boolean.parseBoolean(LinstorConfigurationManager.InsecureSsl.defaultValue());
+        LinstorStoragePool storagePool = new LinstorStoragePool(name, host, 
port, userInfo, type, this, insecureSsl);
 
         MapStorageUuidToStoragePool.put(name, storagePool);
 
@@ -742,7 +760,8 @@ public class LinstorStorageAdaptor implements 
StorageAdaptor {
 
     public long getCapacity(LinstorStoragePool pool) {
         final String rscGroupName = pool.getResourceGroup();
-        return LinstorUtil.getCapacityBytes(pool.getSourceHost(), 
rscGroupName);
+        // Agent side: no per-pool token config; fall back to the host's 
auth.json (or unauthenticated).
+        return LinstorUtil.getCapacityBytes(pool.getSourceHost(), 
rscGroupName, null, pool.isInsecureSsl());
     }
 
     public long getAvailable(LinstorStoragePool pool) {
diff --git 
a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java
 
b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java
index 1bcfaa4ebf7..8a02560fd46 100644
--- 
a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java
+++ 
b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java
@@ -46,16 +46,18 @@ public class LinstorStoragePool implements KVMStoragePool {
     private final Storage.StoragePoolType _storagePoolType;
     private final StorageAdaptor _storageAdaptor;
     private final String _resourceGroup;
+    private final boolean _insecureSsl;
     private final String localNodeName;
 
     public LinstorStoragePool(String uuid, String host, int port, String 
resourceGroup,
-                              Storage.StoragePoolType storagePoolType, 
StorageAdaptor storageAdaptor) {
+                              Storage.StoragePoolType storagePoolType, 
StorageAdaptor storageAdaptor, boolean insecureSsl) {
         _uuid = uuid;
         _sourceHost = host;
         _sourcePort = port;
         _storagePoolType = storagePoolType;
         _storageAdaptor = storageAdaptor;
         _resourceGroup = resourceGroup;
+        _insecureSsl = insecureSsl;
         localNodeName = getHostname();
     }
 
@@ -213,6 +215,10 @@ public class LinstorStoragePool implements KVMStoragePool {
         return _resourceGroup;
     }
 
+    public boolean isInsecureSsl() {
+        return _insecureSsl;
+    }
+
     @Override
     public boolean isPoolSupportHA() {
         return true;
diff --git 
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
index 3f06bee8ac8..672731fd07c 100644
--- 
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
+++ 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
@@ -31,6 +31,7 @@ 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;
+import com.linbit.linstor.api.model.SnapshotRollback;
 import com.linbit.linstor.api.model.VolumeDefinitionModify;
 
 import javax.annotation.Nonnull;
@@ -214,9 +215,15 @@ public class LinstorPrimaryDataStoreDriverImpl implements 
PrimaryDataStoreDriver
         return LinstorUtil.RSC_PREFIX + snapshotUuid;
     }
 
+    private DevelopersApi getLinstorAPI(StoragePool pool) {
+        return LinstorUtil.getLinstorAPI(pool.getHostAddress(),
+                LinstorConfigurationManager.ApiToken.valueIn(pool.getId()),
+                
Boolean.TRUE.equals(LinstorConfigurationManager.InsecureSsl.valueIn(pool.getId())));
+    }
+
     private void deleteResourceDefinition(StoragePoolVO storagePoolVO, String 
rscDefName)
     {
-        DevelopersApi linstorApi = 
LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+        DevelopersApi linstorApi = getLinstorAPI(storagePoolVO);
 
         try
         {
@@ -240,7 +247,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements 
PrimaryDataStoreDriver
     private void deleteSnapshot(@Nonnull DataStore dataStore, @Nonnull String 
rscDefName, @Nonnull String snapshotName)
     {
         StoragePoolVO storagePool = 
_storagePoolDao.findById(dataStore.getId());
-        DevelopersApi linstorApi = 
LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+        DevelopersApi linstorApi = getLinstorAPI(storagePool);
 
         try
         {
@@ -411,7 +418,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements 
PrimaryDataStoreDriver
             }
             final String rscName = LinstorUtil.RSC_PREFIX + 
volumeInfo.getUuid();
 
-            final DevelopersApi linstorApi = 
LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+            final DevelopersApi linstorApi = getLinstorAPI(storagePoolVO);
 
             try {
                 ResourceDefinition templateRD = 
LinstorUtil.findResourceDefinition(
@@ -474,7 +481,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements 
PrimaryDataStoreDriver
 
     private String createResourceFromSnapshot(long csSnapshotId, String 
rscName, StoragePoolVO storagePoolVO) {
         final String rscGrp = LinstorUtil.getRscGrp(storagePoolVO);
-        final DevelopersApi linstorApi = 
LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+        final DevelopersApi linstorApi = getLinstorAPI(storagePoolVO);
 
         SnapshotVO snapshotVO = _snapshotDao.findById(csSnapshotId);
         String snapName = LinstorUtil.RSC_PREFIX + snapshotVO.getUuid();
@@ -672,14 +679,14 @@ public class LinstorPrimaryDataStoreDriverImpl implements 
PrimaryDataStoreDriver
 
     private String doRevertSnapshot(final SnapshotInfo snapshot, final 
VolumeInfo volumeInfo) {
         final StoragePool pool = (StoragePool) volumeInfo.getDataStore();
-        final DevelopersApi linstorApi = 
LinstorUtil.getLinstorAPI(pool.getHostAddress());
+        final DevelopersApi linstorApi = getLinstorAPI(pool);
         final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getPath();
         String resultMsg;
         try {
             if (snapshot.getDataStore().getRole() == DataStoreRole.Primary) {
                 final String snapName = LinstorUtil.RSC_PREFIX + 
snapshot.getUuid();
 
-                ApiCallRcList answers = 
linstorApi.resourceSnapshotRollback(rscName, snapName);
+                ApiCallRcList answers = 
linstorApi.resourceSnapshotRollback(rscName, snapName, new SnapshotRollback());
                 resultMsg = checkLinstorAnswers(answers);
             } else if (snapshot.getDataStore().getRole() == 
DataStoreRole.Image) {
                 resultMsg = revertSnapshotFromImageStore(snapshot, volumeInfo, 
linstorApi, rscName);
@@ -913,7 +920,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements 
PrimaryDataStoreDriver
     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 DevelopersApi api = getLinstorAPI(pool);
         final String rscName = LinstorUtil.RSC_PREFIX + dstData.getUuid();
         boolean newCreated = LinstorUtil.createResourceBase(
             LinstorUtil.RSC_PREFIX + dstData.getUuid(),
@@ -961,7 +968,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements 
PrimaryDataStoreDriver
     private Answer copyVolume(DataObject srcData, DataObject dstData) {
         VolumeInfo srcVolInfo = (VolumeInfo) srcData;
         final StoragePoolVO pool = 
_storagePoolDao.findById(srcVolInfo.getDataStore().getId());
-        final DevelopersApi api = 
LinstorUtil.getLinstorAPI(pool.getHostAddress());
+        final DevelopersApi api = getLinstorAPI(pool);
         final String rscName = LinstorUtil.RSC_PREFIX + srcVolInfo.getPath();
 
         VolumeObjectTO to = (VolumeObjectTO) srcVolInfo.getTO();
@@ -1066,7 +1073,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements 
PrimaryDataStoreDriver
         SnapshotObject snapshotObject = (SnapshotObject)srcData;
         Boolean snapshotFullBackup = snapshotObject.getFullBackup();
         final StoragePoolVO pool = 
_storagePoolDao.findById(srcData.getDataStore().getId());
-        final DevelopersApi api = 
LinstorUtil.getLinstorAPI(pool.getHostAddress());
+        final DevelopersApi api = getLinstorAPI(pool);
         boolean fullSnapshot = true;
         if (snapshotFullBackup != null) {
             fullSnapshot = snapshotFullBackup;
@@ -1147,7 +1154,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements 
PrimaryDataStoreDriver
     {
         final VolumeObject vol = (VolumeObject) data;
         final StoragePoolVO pool = 
_storagePoolDao.findById(data.getDataStore().getId());
-        final DevelopersApi api = 
LinstorUtil.getLinstorAPI(pool.getHostAddress());
+        final DevelopersApi api = getLinstorAPI(pool);
         final ResizeVolumePayload resizeParameter = (ResizeVolumePayload) 
vol.getpayload();
 
         final String rscName = LinstorUtil.RSC_PREFIX + vol.getPath();
@@ -1221,7 +1228,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements 
PrimaryDataStoreDriver
 
         long storagePoolId = volumeVO.getPoolId();
         final StoragePoolVO storagePool = 
_storagePoolDao.findById(storagePoolId);
-        final DevelopersApi api = 
LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+        final DevelopersApi api = getLinstorAPI(storagePool);
         final String rscName = LinstorUtil.RSC_PREFIX + volumeVO.getPath();
 
         Snapshot snapshot = new Snapshot();
@@ -1265,7 +1272,9 @@ public class LinstorPrimaryDataStoreDriverImpl implements 
PrimaryDataStoreDriver
     @Override
     public Pair<Long, Long> getStorageStats(StoragePool storagePool) {
         logger.debug(String.format("Requesting storage stats: %s", 
storagePool));
-        return LinstorUtil.getStorageStats(storagePool.getHostAddress(), 
LinstorUtil.getRscGrp(storagePool));
+        return LinstorUtil.getStorageStats(storagePool.getHostAddress(), 
LinstorUtil.getRscGrp(storagePool),
+                
LinstorConfigurationManager.ApiToken.valueIn(storagePool.getId()),
+                
Boolean.TRUE.equals(LinstorConfigurationManager.InsecureSsl.valueIn(storagePool.getId())));
     }
 
     @Override
@@ -1277,8 +1286,8 @@ public class LinstorPrimaryDataStoreDriverImpl implements 
PrimaryDataStoreDriver
      * Updates the cache map containing current allocated size data.
      * @param linstorAddr Linstor cluster api address
      */
-    private void fillVolumeStatsCache(String linstorAddr) {
-        final DevelopersApi api = LinstorUtil.getLinstorAPI(linstorAddr);
+    private void fillVolumeStatsCache(String linstorAddr, String apiToken, 
boolean insecureSsl) {
+        final DevelopersApi api = LinstorUtil.getLinstorAPI(linstorAddr, 
apiToken, insecureSsl);
         try {
             logger.trace("Start volume stats cache update for " + linstorAddr);
             List<ResourceWithVolumes> resources = api.viewResources(
@@ -1327,7 +1336,9 @@ public class LinstorPrimaryDataStoreDriverImpl implements 
PrimaryDataStoreDriver
             long invalidateCacheTime = 
volumeStatsLastUpdate.getOrDefault(storagePool.getHostAddress(), 0L) +
                     LinstorConfigurationManager.VolumeStatsCacheTime.value() * 
1000;
             if (invalidateCacheTime < System.currentTimeMillis()) {
-                fillVolumeStatsCache(storagePool.getHostAddress());
+                fillVolumeStatsCache(storagePool.getHostAddress(),
+                        
LinstorConfigurationManager.ApiToken.valueIn(storagePool.getId()),
+                        
Boolean.TRUE.equals(LinstorConfigurationManager.InsecureSsl.valueIn(storagePool.getId())));
             }
             String volumeKey = linstorAddr + "/" + LinstorUtil.RSC_PREFIX + 
volumeId;
             Pair<Long, Long> sizePair = volumeStats.get(volumeKey);
diff --git 
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java
 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java
index fa9c1b71ff3..7c1330b9088 100644
--- 
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java
+++ 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java
@@ -40,6 +40,7 @@ import com.cloud.storage.StorageManager;
 import com.cloud.storage.StoragePool;
 import com.cloud.storage.StoragePoolAutomation;
 import com.cloud.storage.dao.StoragePoolAndAccessGroupMapDao;
+import com.cloud.utils.crypt.DBEncryptionUtil;
 import com.cloud.utils.exception.CloudRuntimeException;
 import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
@@ -49,9 +50,12 @@ import 
org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCy
 import 
org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreParameters;
 import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import 
org.apache.cloudstack.storage.datastore.util.LinstorConfigurationManager;
 import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
 import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper;
+import org.apache.commons.lang3.StringUtils;
 
 public class LinstorPrimaryDataStoreLifeCycleImpl extends 
BasePrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle {
     @Inject
@@ -65,6 +69,8 @@ public class LinstorPrimaryDataStoreLifeCycleImpl extends 
BasePrimaryDataStoreLi
     @Inject
     PrimaryDataStoreHelper dataStoreHelper;
     @Inject
+    private StoragePoolDetailsDao storagePoolDetailsDao;
+    @Inject
     private StoragePoolAutomation storagePoolAutomation;
     @Inject
     private CapacityManager _capacityMgr;
@@ -122,7 +128,15 @@ public class LinstorPrimaryDataStoreLifeCycleImpl extends 
BasePrimaryDataStoreLi
             }
         }
 
-        if (!url.contains("://")) {
+        // The linstor client accepts the "linstor://" (HTTP, default port 
3370) and "linstor+ssl://"
+        // (HTTPS, default port 3371) scheme aliases, but java.net.URL only 
understands http/https.
+        // Normalize the aliases so the URL parses and is stored in a scheme 
the client connects with.
+        final String lowerUrl = url.toLowerCase();
+        if (lowerUrl.startsWith("linstor+ssl://")) {
+            url = "https://"; + url.substring("linstor+ssl://".length());
+        } else if (lowerUrl.startsWith("linstor://")) {
+            url = "http://"; + url.substring("linstor://".length());
+        } else if (!url.contains("://")) {
             url = "http://"; + url;
         }
 
@@ -146,7 +160,18 @@ public class LinstorPrimaryDataStoreLifeCycleImpl extends 
BasePrimaryDataStoreLi
             throw new IllegalArgumentException("Linstor controller URL is not 
valid: " + e);
         }
 
-        long capacityBytes = LinstorUtil.getCapacityBytes(url, resourceGroup);
+        // The per-pool token config does not exist yet at creation time, so 
the token (when the
+        // controller requires one) may be supplied directly in the add-pool 
details. Pull it out of
+        // the details map so it is not persisted in clear text by the generic 
details handling, use it
+        // for the initial capacity probe, and store it (encrypted) as the 
per-pool config once the pool
+        // exists (below). When no token is given we fall back to the 
management server's auth.json (or
+        // an unauthenticated controller). The self-signed/insecure TLS flag 
is read the same way.
+        final String apiToken = details != null ? 
details.remove(LinstorConfigurationManager.ApiToken.key()) : null;
+        final String insecureSslDetail = details != null ? 
details.get(LinstorConfigurationManager.InsecureSsl.key()) : null;
+        final boolean insecureSsl = insecureSslDetail != null
+                ? Boolean.parseBoolean(insecureSslDetail)
+                : 
Boolean.parseBoolean(LinstorConfigurationManager.InsecureSsl.defaultValue());
+        long capacityBytes = LinstorUtil.getCapacityBytes(url, resourceGroup, 
apiToken, insecureSsl);
         if (capacityBytes <= 0) {
             throw new IllegalArgumentException("'capacityBytes' must be 
present and greater than 0.");
         }
@@ -173,7 +198,16 @@ public class LinstorPrimaryDataStoreLifeCycleImpl extends 
BasePrimaryDataStoreLi
         parameters.setDetails(details);
         parameters.setUserInfo(resourceGroup);
 
-        return dataStoreHelper.createPrimaryDataStore(parameters);
+        DataStore dataStore = 
dataStoreHelper.createPrimaryDataStore(parameters);
+
+        if (dataStore != null && StringUtils.isNotEmpty(apiToken)) {
+            // lin.auth.apitoken is a "Secure" config, so its value must be 
stored encrypted for
+            // ConfigKey.valueIn() to be able to decrypt it on read.
+            storagePoolDetailsDao.addDetail(dataStore.getId(),
+                    LinstorConfigurationManager.ApiToken.key(), 
DBEncryptionUtil.encrypt(apiToken), false);
+        }
+
+        return dataStore;
     }
 
     protected boolean createStoragePool(Host host, StoragePool pool) {
diff --git 
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigChangeListener.java
 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigChangeListener.java
new file mode 100644
index 00000000000..cec3fb68ea9
--- /dev/null
+++ 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigChangeListener.java
@@ -0,0 +1,108 @@
+// 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 org.apache.cloudstack.storage.datastore.util;
+
+import java.util.Collections;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.messagebus.MessageBus;
+import org.apache.cloudstack.framework.messagebus.MessageSubscriber;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import com.cloud.event.EventTypes;
+import com.cloud.host.Host;
+import com.cloud.host.dao.HostDao;
+import com.cloud.storage.Storage;
+import com.cloud.storage.StorageManager;
+import com.cloud.storage.dao.StoragePoolHostDao;
+import com.cloud.utils.Ternary;
+import com.cloud.utils.component.ManagerBase;
+
+/**
+ * Management-server only component. Per-pool Linstor settings that the agent 
needs (the insecure-TLS
+ * flag) are delivered to the agent inside the storage pool details of a 
ModifyStoragePoolCommand and
+ * then cached in the agent's LinstorStoragePool. A dynamic {@code 
updateConfiguration} only updates the
+ * database and the management server's own config cache; it does not refresh 
the agent. This listener
+ * reacts to such changes and re-pushes the pool details to every connected 
host so the cached pool is
+ * rebuilt with the new value, without requiring a host reconnect.
+ */
+public class LinstorConfigChangeListener extends ManagerBase {
+    protected Logger logger = LogManager.getLogger(getClass());
+
+    @Inject
+    private MessageBus messageBus;
+    @Inject
+    private PrimaryDataStoreDao primaryDataStoreDao;
+    @Inject
+    private StoragePoolHostDao storagePoolHostDao;
+    @Inject
+    private HostDao hostDao;
+    @Inject
+    private StorageManager storageManager;
+
+    @Override
+    public boolean configure(String name, Map<String, Object> params) throws 
ConfigurationException {
+        super.configure(name, params);
+        messageBus.subscribe(EventTypes.EVENT_CONFIGURATION_VALUE_EDIT, new 
ConfigValueChangeSubscriber());
+        return true;
+    }
+
+    private final class ConfigValueChangeSubscriber implements 
MessageSubscriber {
+        @Override
+        @SuppressWarnings("unchecked")
+        public void onPublishMessage(String senderAddress, String subject, 
Object args) {
+            if (!(args instanceof Ternary)) {
+                return;
+            }
+            final Ternary<String, ConfigKey.Scope, Long> updated = 
(Ternary<String, ConfigKey.Scope, Long>) args;
+            // Only the per-pool insecure-TLS flag has to reach the agent; the 
API token is read from
+            // the agent's local auth.json, so it never needs a re-push.
+            if (ConfigKey.Scope.StoragePool != updated.second()
+                    || 
!LinstorConfigurationManager.InsecureSsl.key().equals(updated.first())) {
+                return;
+            }
+
+            final Long poolId = updated.third();
+            final StoragePoolVO pool = primaryDataStoreDao.findById(poolId);
+            if (pool == null || pool.getPoolType() != 
Storage.StoragePoolType.Linstor) {
+                return;
+            }
+
+            logger.debug("Linstor: {} changed for storage pool {}, re-pushing 
pool details to connected hosts",
+                    updated.first(), poolId);
+            for (Long hostId : 
storagePoolHostDao.findHostsConnectedToPools(Collections.singletonList(poolId)))
 {
+                final Host host = hostDao.findById(hostId);
+                if (host == null) {
+                    continue;
+                }
+                try {
+                    storageManager.connectHostToSharedPool(host, poolId);
+                    logger.debug("Linstor: re-pushed pool {} details to host 
{}", poolId, hostId);
+                } catch (Exception e) {
+                    logger.warn("Linstor: failed to re-push pool {} details to 
host {}", poolId, hostId, e);
+                }
+            }
+        }
+    }
+}
diff --git 
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigurationManager.java
 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigurationManager.java
index 85a0804dbab..0292eef64a5 100644
--- 
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigurationManager.java
+++ 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorConfigurationManager.java
@@ -29,8 +29,15 @@ public class LinstorConfigurationManager implements 
Configurable
             "Cache time of volume stats for Linstor volumes. 0 to disable 
volume stats",
             false);
 
+    public static final ConfigKey<String> ApiToken = new 
ConfigKey<>(String.class, "lin.auth.apitoken", "Secure", "",
+            "API token used to authenticate on the Controller", true, 
ConfigKey.Scope.StoragePool, null);
+
+    public static final ConfigKey<Boolean> InsecureSsl = new 
ConfigKey<>(Boolean.class, "lin.ssl.insecure", "Advanced", "true",
+            "Allow self-signed/untrusted TLS certificates from the Linstor 
controller (disables certificate and hostname verification)",
+            true, ConfigKey.Scope.StoragePool, null);
+
     public static final ConfigKey<?>[] CONFIG_KEYS = new ConfigKey<?>[] {
-            BackupSnapshots, VolumeStatsCacheTime
+            ApiToken, BackupSnapshots, InsecureSsl, VolumeStatsCacheTime
     };
 
     @Override
diff --git 
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java
 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java
index 7c45493dddc..67c070f84eb 100644
--- 
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java
+++ 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java
@@ -51,6 +51,7 @@ import java.util.Optional;
 import java.util.stream.Collectors;
 
 import com.cloud.hypervisor.kvm.storage.KVMStoragePool;
+import com.cloud.hypervisor.kvm.storage.LinstorStoragePool;
 import com.cloud.utils.Pair;
 import com.cloud.utils.exception.CloudRuntimeException;
 import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
@@ -78,8 +79,25 @@ public class LinstorUtil {
     public static final String CLUSTER_DEFAULT_MAX_IOPS = 
"clusterDefaultMaxIops";
 
     public static DevelopersApi getLinstorAPI(String linstorUrl) {
+        return getLinstorAPI(linstorUrl, null, false);
+    }
+
+    public static DevelopersApi getLinstorAPI(String linstorUrl, String 
apiToken) {
+        return getLinstorAPI(linstorUrl, apiToken, false);
+    }
+
+    public static DevelopersApi getLinstorAPI(String linstorUrl, String 
apiToken, boolean insecureSsl) {
         ApiClient client = new ApiClient();
         client.setBasePath(linstorUrl);
+        // An explicit (non-empty) token wins; otherwise the client falls back 
to the auth.json file
+        // on the local host (used on the agent side). No token at all -> 
unauthenticated controller.
+        client.setAccessTokenWithFallback(apiToken);
+        // Trust self-signed/untrusted controller certificates when explicitly 
allowed (rebuilds the
+        // http client, so set this before discovering HTTPS).
+        if (insecureSsl) {
+            client.setInsecureSsl();
+        }
+        client.discoverHttps();
         return new DevelopersApi(client);
     }
 
@@ -224,8 +242,8 @@ public class LinstorUtil {
         );
     }
 
-    public static long getCapacityBytes(String linstorUrl, String 
rscGroupName) {
-        DevelopersApi linstorApi = getLinstorAPI(linstorUrl);
+    public static long getCapacityBytes(String linstorUrl, String 
rscGroupName, String apiToken, boolean insecureSsl) {
+        DevelopersApi linstorApi = getLinstorAPI(linstorUrl, apiToken, 
insecureSsl);
         try {
             List<StoragePool> storagePools = 
getRscGroupStoragePools(linstorApi, rscGroupName);
 
@@ -239,8 +257,8 @@ public class LinstorUtil {
         }
     }
 
-    public static Pair<Long, Long> getStorageStats(String linstorUrl, String 
rscGroupName) {
-        DevelopersApi linstorApi = getLinstorAPI(linstorUrl);
+    public static Pair<Long, Long> getStorageStats(String linstorUrl, String 
rscGroupName, String apiToken, boolean insecureSsl) {
+        DevelopersApi linstorApi = getLinstorAPI(linstorUrl, apiToken, 
insecureSsl);
         try {
             List<StoragePool> storagePools = 
LinstorUtil.getRscGroupStoragePools(linstorApi, rscGroupName);
 
@@ -505,7 +523,8 @@ public class LinstorUtil {
      * @return true if all resources are on a provider with zeroed blocks.
      */
     public static boolean resourceSupportZeroBlocks(KVMStoragePool pool, 
String resName) {
-        final DevelopersApi api = getLinstorAPI(pool.getSourceHost());
+        final boolean insecureSsl = pool instanceof LinstorStoragePool && 
((LinstorStoragePool) pool).isInsecureSsl();
+        final DevelopersApi api = getLinstorAPI(pool.getSourceHost(), null, 
insecureSsl);
         try {
             List<ResourceWithVolumes> resWithVols = api.viewResources(
                     Collections.emptyList(),
@@ -760,7 +779,9 @@ public class LinstorUtil {
 
     public static String createResource(VolumeInfo vol, StoragePoolVO 
storagePoolVO,
                                         PrimaryDataStoreDao 
primaryDataStoreDao, boolean exactSize) {
-        DevelopersApi linstorApi = 
LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+        DevelopersApi linstorApi = 
LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress(),
+                
LinstorConfigurationManager.ApiToken.valueIn(storagePoolVO.getId()),
+                
Boolean.TRUE.equals(LinstorConfigurationManager.InsecureSsl.valueIn(storagePoolVO.getId())));
         final String rscGrp = getRscGrp(storagePoolVO);
 
         final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
diff --git 
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/motion/LinstorDataMotionStrategy.java
 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/motion/LinstorDataMotionStrategy.java
index cab2820f09a..f64837e4832 100644
--- 
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/motion/LinstorDataMotionStrategy.java
+++ 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/motion/LinstorDataMotionStrategy.java
@@ -70,6 +70,7 @@ import org.apache.cloudstack.storage.command.CopyCmdAnswer;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import 
org.apache.cloudstack.storage.datastore.util.LinstorConfigurationManager;
 import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections.MapUtils;
@@ -170,7 +171,9 @@ public class LinstorDataMotionStrategy implements 
DataMotionStrategy {
 
     private void removeExactSizeProperty(VolumeInfo volumeInfo) {
         StoragePoolVO destStoragePool = 
_storagePool.findById(volumeInfo.getDataStore().getId());
-        DevelopersApi api = 
LinstorUtil.getLinstorAPI(destStoragePool.getHostAddress());
+        DevelopersApi api = 
LinstorUtil.getLinstorAPI(destStoragePool.getHostAddress(),
+                
LinstorConfigurationManager.ApiToken.valueIn(destStoragePool.getId()),
+                
Boolean.TRUE.equals(LinstorConfigurationManager.InsecureSsl.valueIn(destStoragePool.getId())));
 
         ResourceDefinitionModify rdm = new ResourceDefinitionModify();
         
rdm.setDeleteProps(Collections.singletonList(LinstorUtil.LIN_PROP_DRBDOPT_EXACT_SIZE));
@@ -290,7 +293,9 @@ public class LinstorDataMotionStrategy implements 
DataMotionStrategy {
     private boolean needsExactSizeProp(VolumeInfo srcVolumeInfo) {
         StoragePoolVO srcStoragePool = 
_storagePool.findById(srcVolumeInfo.getDataStore().getId());
         if (srcStoragePool.getPoolType() == Storage.StoragePoolType.Linstor) {
-            DevelopersApi api = 
LinstorUtil.getLinstorAPI(srcStoragePool.getHostAddress());
+            DevelopersApi api = 
LinstorUtil.getLinstorAPI(srcStoragePool.getHostAddress(),
+                    
LinstorConfigurationManager.ApiToken.valueIn(srcStoragePool.getId()),
+                    
Boolean.TRUE.equals(LinstorConfigurationManager.InsecureSsl.valueIn(srcStoragePool.getId())));
 
             String rscName = LinstorUtil.RSC_PREFIX + srcVolumeInfo.getPath();
             try {
diff --git 
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/snapshot/LinstorVMSnapshotStrategy.java
 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/snapshot/LinstorVMSnapshotStrategy.java
index 4e4c882ae80..d0f82484694 100644
--- 
a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/snapshot/LinstorVMSnapshotStrategy.java
+++ 
b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/snapshot/LinstorVMSnapshotStrategy.java
@@ -23,6 +23,7 @@ import com.linbit.linstor.api.DevelopersApi;
 import com.linbit.linstor.api.model.ApiCallRcList;
 import com.linbit.linstor.api.model.CreateMultiSnapshotRequest;
 import com.linbit.linstor.api.model.Snapshot;
+import com.linbit.linstor.api.model.SnapshotRollback;
 
 import javax.inject.Inject;
 
@@ -49,6 +50,7 @@ import com.cloud.vm.snapshot.dao.VMSnapshotDao;
 import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import 
org.apache.cloudstack.storage.datastore.util.LinstorConfigurationManager;
 import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
 import org.apache.cloudstack.storage.to.VolumeObjectTO;
 import org.apache.cloudstack.storage.vmsnapshot.DefaultVMSnapshotStrategy;
@@ -137,7 +139,10 @@ public class LinstorVMSnapshotStrategy extends 
DefaultVMSnapshotStrategy {
         try {
             final List<VolumeObjectTO> volumeTOs = 
_vmSnapshotHelper.getVolumeTOList(userVm.getId());
             final StoragePoolVO storagePool = 
_storagePoolDao.findById(volumeTOs.get(0).getPoolId());
-            final DevelopersApi api = 
LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+            final DevelopersApi api = LinstorUtil.getLinstorAPI(
+                    storagePool.getHostAddress(),
+                    
LinstorConfigurationManager.ApiToken.valueIn(storagePool.getId()),
+                    
Boolean.TRUE.equals(LinstorConfigurationManager.InsecureSsl.valueIn(storagePool.getId())));
 
             long prev_chain_size = 0;
             long virtual_size = 0;
@@ -235,7 +240,10 @@ public class LinstorVMSnapshotStrategy extends 
DefaultVMSnapshotStrategy {
 
         List<VolumeObjectTO> volumeTOs = 
_vmSnapshotHelper.getVolumeTOList(vmSnapshot.getVmId());
         final StoragePoolVO storagePool = 
_storagePoolDao.findById(volumeTOs.get(0).getPoolId());
-        final DevelopersApi api = 
LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+        final DevelopersApi api = LinstorUtil.getLinstorAPI(
+                storagePool.getHostAddress(),
+                
LinstorConfigurationManager.ApiToken.valueIn(storagePool.getId()),
+                
Boolean.TRUE.equals(LinstorConfigurationManager.InsecureSsl.valueIn(storagePool.getId())));
 
         final String snapshotName = vmSnapshotVO.getName();
         final List<String> failedToDelete = new ArrayList<>();
@@ -272,7 +280,7 @@ public class LinstorVMSnapshotStrategy extends 
DefaultVMSnapshotStrategy {
     private String linstorRevertSnapshot(final DevelopersApi api, final String 
rscName, final String snapshotName) {
         String resultMsg = null;
         try {
-            ApiCallRcList answers = api.resourceSnapshotRollback(rscName, 
snapshotName);
+            ApiCallRcList answers = api.resourceSnapshotRollback(rscName, 
snapshotName, new SnapshotRollback());
             if (answers.hasError()) {
                 resultMsg = LinstorUtil.getBestErrorMessage(answers);
             }
@@ -289,7 +297,10 @@ public class LinstorVMSnapshotStrategy extends 
DefaultVMSnapshotStrategy {
         List<VolumeObjectTO> volumeTOs = 
_vmSnapshotHelper.getVolumeTOList(userVmId);
 
         final StoragePoolVO storagePool = 
_storagePoolDao.findById(volumeTOs.get(0).getPoolId());
-        final DevelopersApi api = 
LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+        final DevelopersApi api = LinstorUtil.getLinstorAPI(
+                storagePool.getHostAddress(),
+                
LinstorConfigurationManager.ApiToken.valueIn(storagePool.getId()),
+                
Boolean.TRUE.equals(LinstorConfigurationManager.InsecureSsl.valueIn(storagePool.getId())));
         final String snapshotName = vmSnapshotVO.getName();
 
         for (VolumeObjectTO volumeObjectTO : volumeTOs) {
diff --git 
a/plugins/storage/volume/linstor/src/main/resources/META-INF/cloudstack/storage-volume-linstor/spring-storage-volume-linstor-context.xml
 
b/plugins/storage/volume/linstor/src/main/resources/META-INF/cloudstack/storage-volume-linstor/spring-storage-volume-linstor-context.xml
index 88d1051c71e..ff46be9352a 100644
--- 
a/plugins/storage/volume/linstor/src/main/resources/META-INF/cloudstack/storage-volume-linstor/spring-storage-volume-linstor-context.xml
+++ 
b/plugins/storage/volume/linstor/src/main/resources/META-INF/cloudstack/storage-volume-linstor/spring-storage-volume-linstor-context.xml
@@ -33,6 +33,8 @@
           
class="org.apache.cloudstack.storage.snapshot.LinstorVMSnapshotStrategy" />
     <bean id="linstorConfigManager"
           
class="org.apache.cloudstack.storage.datastore.util.LinstorConfigurationManager"
 />
+    <bean id="linstorConfigChangeListener"
+          
class="org.apache.cloudstack.storage.datastore.util.LinstorConfigChangeListener"
 />
     <bean id="linstorDataMotionStrategy"
           
class="org.apache.cloudstack.storage.motion.LinstorDataMotionStrategy" />
 </beans>
diff --git a/pom.xml b/pom.xml
index 8392a099b57..1c7ae233d78 100644
--- a/pom.xml
+++ b/pom.xml
@@ -173,7 +173,7 @@
         <cs.nitro.version>10.1</cs.nitro.version>
         <cs.opensaml.version>2.6.6</cs.opensaml.version>
         <cs.rados-java.version>0.6.0</cs.rados-java.version>
-        <cs.java-linstor.version>0.6.1</cs.java-linstor.version>
+        <cs.java-linstor.version>0.7.0</cs.java-linstor.version>
         <cs.reflections.version>0.10.2</cs.reflections.version>
         <cs.servicemix.version>3.4.4_1</cs.servicemix.version>
         <cs.servlet.version>4.0.1</cs.servlet.version>
diff --git a/test/integration/plugins/linstor/test_linstor_volumes.py 
b/test/integration/plugins/linstor/test_linstor_volumes.py
index e0ba15a0499..c2c220bfe9e 100644
--- a/test/integration/plugins/linstor/test_linstor_volumes.py
+++ b/test/integration/plugins/linstor/test_linstor_volumes.py
@@ -16,6 +16,7 @@
 # under the License.
 
 import logging
+import os
 import random
 import time
 import socket
@@ -283,6 +284,17 @@ class TestLinstorVolumes(cloudstackTestCase):
 
         cls.testdata = TestData(first_host.ipaddress).testdata
 
+        # Registering a Linstor pool makes the management server read the 
resource-group capacity from
+        # the controller. If the controller enforces authentication, that call 
needs an API token,
+        # supplied as the 'lin.auth.apitoken' add-pool detail. Provide it via 
LINSTOR_API_TOKEN so it is
+        # never hard-coded; leave it unset for an unauthenticated controller.
+        api_token = os.environ.get("LINSTOR_API_TOKEN")
+        if api_token:
+            for storage_key in (TestData.primaryStorage,
+                                 TestData.primaryStorageSameInstance,
+                                 TestData.primaryStorageDistinctInstance):
+                cls.testdata[storage_key]["details"]["lin.auth.apitoken"] = 
api_token
+
         # Get Resources from Cloud Infrastructure
         cls.zone = get_zone(cls.apiClient, 
zone_id=cls.testdata[TestData.zoneId])
         cls.cluster = list_clusters(cls.apiClient)[0]
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index fe3678c9f37..18b76f102d4 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -2164,6 +2164,8 @@
 "label.routeripv6": "IPv6 address for the VR in this Network.",
 "label.routing.firewall": "IPv4 Routing Firewall",
 "label.resourcegroup": "Resource group",
+"label.linstor.apitoken": "Controller API token",
+"label.linstor.ssl.insecure": "Allow self-signed certificate",
 "label.routingmode": "Routing mode",
 "label.routing.policy": "Routing policy",
 "label.routing.policy.terms": "Routing policy terms",
@@ -3625,6 +3627,8 @@
 "message.launch.zone.hint": "Configure Network components and traffic 
including IP addresses.",
 "message.license.agreements.not.accepted": "License agreements not accepted.",
 "message.linstor.resourcegroup.description": "Linstor resource group to use 
for primary storage.",
+"message.linstor.apitoken.description": "API token used to authenticate 
against the Linstor controller. Leave empty to use an auth.json file on the 
management server and hosts, or for an unauthenticated controller.",
+"message.linstor.ssl.insecure.description": "Trust self-signed/untrusted TLS 
certificates from the Linstor controller (disables certificate and hostname 
verification).",
 "message.list.zone.vmware.datacenter.empty": "No VMware Datacenter exists in 
the selected Zone",
 "message.list.zone.vmware.hosts.empty": "No EXSi hosts were found in the 
selected Datacenter.\nAre the entered credentials correct?\n",
 "message.listnsp.not.return.providerid": "error: listNetworkServiceProviders 
API doesn't return VirtualRouter provider ID.",
diff --git a/ui/src/views/infra/AddPrimaryStorage.vue 
b/ui/src/views/infra/AddPrimaryStorage.vue
index d46396bbb3a..a67c61ff424 100644
--- a/ui/src/views/infra/AddPrimaryStorage.vue
+++ b/ui/src/views/infra/AddPrimaryStorage.vue
@@ -406,6 +406,18 @@
             </template>
             <a-input v-model:value="form.resourcegroup" 
:placeholder="$t('message.linstor.resourcegroup.description')" />
           </a-form-item>
+          <a-form-item name="linstorApiToken" ref="linstorApiToken">
+            <template #label>
+              <tooltip-label :title="$t('label.linstor.apitoken')" 
:tooltip="$t('message.linstor.apitoken.description')"/>
+            </template>
+            <a-input v-model:value="form.linstorApiToken" 
:placeholder="$t('message.linstor.apitoken.description')" />
+          </a-form-item>
+          <a-form-item name="linstorInsecureSsl" ref="linstorInsecureSsl">
+            <template #label>
+              <tooltip-label :title="$t('label.linstor.ssl.insecure')" 
:tooltip="$t('message.linstor.ssl.insecure.description')"/>
+            </template>
+            <a-switch v-model:checked="form.linstorInsecureSsl" />
+          </a-form-item>
         </div>
         <a-form-item name="selectedTags" ref="selectedTags">
           <template #label>
@@ -480,7 +492,8 @@ export default {
       this.form = reactive({
         scope: 'cluster',
         hypervisor: this.hypervisors[0],
-        provider: 'DefaultPrimary'
+        provider: 'DefaultPrimary',
+        linstorInsecureSsl: true
       })
       this.rules = reactive({
         zone: [{ required: true, message: this.$t('label.required') }],
@@ -896,6 +909,10 @@ export default {
           url = this.linstorURL(server)
           values.managed = false
           params['details[0].resourceGroup'] = values.resourcegroup
+          if (values.linstorApiToken && values.linstorApiToken.length > 0) {
+            params['details[0].lin.auth.apitoken'] = values.linstorApiToken
+          }
+          params['details[0].lin.ssl.insecure'] = values.linstorInsecureSsl ? 
'true' : 'false'
           if (values.capacityIops && values.capacityIops.length > 0) {
             params.capacityIops = values.capacityIops.split(',').join('')
           }
diff --git a/ui/src/views/infra/zone/StaticInputsForm.vue 
b/ui/src/views/infra/zone/StaticInputsForm.vue
index c5a296a42b3..21bf0f149a9 100644
--- a/ui/src/views/infra/zone/StaticInputsForm.vue
+++ b/ui/src/views/infra/zone/StaticInputsForm.vue
@@ -187,6 +187,11 @@ export default {
         this.setRules(field)
         const fieldExists = this.isDisplayInput(field)
         if (!fieldExists) {
+          // A display-gated toggle that defaults on (checked: true) must be 
seeded while still
+          // hidden, otherwise it binds to an undefined value and shows off 
once it becomes visible.
+          if ((field.switch || field.checkbox) && field.checked) {
+            this.form[field.key] = true
+          }
           return
         }
         if (field.key === 'agentUserName' && !this.getPrefilled(field)) {
diff --git a/ui/src/views/infra/zone/ZoneWizardAddResources.vue 
b/ui/src/views/infra/zone/ZoneWizardAddResources.vue
index 32b7b10ad6d..c25bef15884 100644
--- a/ui/src/views/infra/zone/ZoneWizardAddResources.vue
+++ b/ui/src/views/infra/zone/ZoneWizardAddResources.vue
@@ -561,6 +561,25 @@ export default {
             primaryStorageProtocol: 'Linstor'
           }
         },
+        {
+          title: 'label.linstor.apitoken',
+          key: 'primaryStorageLinstorApiToken',
+          placeHolder: 'message.linstor.apitoken.description',
+          required: false,
+          display: {
+            primaryStorageProtocol: 'Linstor'
+          }
+        },
+        {
+          title: 'label.linstor.ssl.insecure',
+          key: 'primaryStorageLinstorInsecureSsl',
+          switch: true,
+          checked: true,
+          required: false,
+          display: {
+            primaryStorageProtocol: 'Linstor'
+          }
+        },
         {
           title: 'label.provider',
           key: 'provider',
@@ -568,7 +587,10 @@ export default {
           value: 'DefaultPrimary',
           select: true,
           required: true,
-          options: this.primaryStorageProviders
+          options: this.primaryStorageProviders,
+          hidden: {
+            primaryStorageProtocol: 'Linstor'
+          }
         },
         {
           title: 'label.ismanaged',
diff --git a/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue 
b/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue
index 7406be4db86..b42a0ad33a8 100644
--- a/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue
+++ b/ui/src/views/infra/zone/ZoneWizardLaunchZone.vue
@@ -1562,6 +1562,10 @@ export default {
         url = this.linstorURL(server)
         params.provider = 'Linstor'
         params['details[0].resourceGroup'] = 
this.prefillContent.primaryStorageLinstorResourceGroup
+        if (this.prefillContent.primaryStorageLinstorApiToken) {
+          params['details[0].lin.auth.apitoken'] = 
this.prefillContent.primaryStorageLinstorApiToken
+        }
+        params['details[0].lin.ssl.insecure'] = 
(this.prefillContent.primaryStorageLinstorInsecureSsl === false) ? 'false' : 
'true'
       } else if (protocol === 'vmfs' || protocol === 'datastorecluster') {
         let path = this.prefillContent.primaryStorageVmfsDatacenter
         if (path.substring(0, 1) !== '/') {

Reply via email to