You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by GitBox <gi...@apache.org> on 2021/07/13 08:27:33 UTC

[GitHub] [cloudstack] rp- opened a new pull request #4994: Linstor volume plugin

rp- opened a new pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994


   ### Description
   
   This PR adds a Linstor volume(primary storage) cloudstack driver to cloudstack.
   Basic operations as creating/migrating/deleting are working.
   Snapshots work with Linstor for most storage pool types, but will fail because cloudstack doesn't seem to support
   snapshots of RAW image types(suggestions/ideas are welcome)
   
   It also has a commit to port elastistor jersey-client to version 2.26, so it will not collide with Linstor dependencies (I can put that in a separate PR if needed)
   
   Design doc: https://cwiki.apache.org/confluence/display/CLOUDSTACK/Linstor+storage+plugin
   
   Fixes: #4403 
   
   ### Types of changes
   
   - [ ] Breaking change (fix or feature that would cause existing functionality to change)
   - [X] New feature (non-breaking change which adds functionality)
   - [ ] Bug fix (non-breaking change which fixes an issue)
   - [ ] Enhancement (improves an existing feature and functionality)
   - [ ] Cleanup (Code refactoring and cleanup, that may add test cases)
   
   ### Feature/Enhancement Scale or Bug Severity
   
   #### Feature/Enhancement Scale
   
   - [X] Major
   - [ ] Minor
   
   #### Bug Severity
   
   - [ ] BLOCKER
   - [ ] Critical
   - [ ] Major
   - [ ] Minor
   - [ ] Trivial
   
   ### How Has This Been Tested?
   4 Node cloudstack cluster (1 Mgmt node, 3 KVM-hosts) with equivalent Linstor setup(1 Controller, 3 Satellites)
   Attaching volumes to VM's, migrating volumes between NFS storage and Linstor, creating templates from Linstor volumes
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] sureshanaparti commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
sureshanaparti commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r626289003



##########
File path: plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
##########
@@ -0,0 +1,420 @@
+// 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.driver;
+
+import com.linbit.linstor.api.ApiCallChecker;
+import com.linbit.linstor.api.ApiException;
+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.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
+import com.linbit.linstor.api.model.VolumeDefinitionModify;
+
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.to.DataObjectType;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.host.Host;
+import com.cloud.storage.ResizeVolumePayload;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
+import org.apache.cloudstack.storage.volume.VolumeObject;
+import org.apache.log4j.Logger;
+
+public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver {
+    private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class);
+    @Inject
+    private PrimaryDataStoreDao _storagePoolDao;
+    @Inject
+    private VolumeDao _volumeDao;
+
+    public LinstorPrimaryDataStoreDriverImpl()
+    {
+    }
+
+    @Override
+    public Map<String, String> getCapabilities()
+    {
+        Map<String, String> mapCapabilities = new HashMap<>();
+
+        // Linstor can create a volume from a volume via snapshot restore, but this would only work
+        // if a LVM-thin storage pool is used, TBH it would also work with ZFS pools, but there you always
+        // have to keep in mind the child-parent snapshot relationship
+        // So we will rely on a plain copy volume mechanism which is more robust for now
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString(), Boolean.FALSE.toString());
+
+        // for now, technically possible
+        mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.FALSE.toString());
+        // CAN_CREATE_VOLUME_FROM_SNAPSHOT see note from CAN_CREATE_VOLUME_FROM_VOLUME
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.FALSE.toString());
+        mapCapabilities.put(DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString(), Boolean.FALSE.toString());
+
+        mapCapabilities.put(DataStoreCapabilities.VOLUME_SNAPSHOT_QUIESCEVM.toString(), Boolean.FALSE.toString());
+
+        return mapCapabilities;
+    }
+
+    @Override
+    public DataTO getTO(DataObject data)
+    {
+        return null;
+    }
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store)
+    {
+        return null;
+    }
+
+    @Override
+    public ChapInfo getChapInfo(DataObject dataObject)
+    {
+        return null;
+    }
+
+    @Override
+    public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+        return false;
+    }
+
+    @Override
+    public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+    }
+
+    @Override
+    public long getUsedBytes(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getUsedIops(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool)
+    {
+        return dataObject.getSize();
+    }
+
+    @Override
+    public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    private void deleteResourceDefinition(DataStore dataStore, String rscDefName)
+    {
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceDefinitionDelete(rscDefName);
+            if (ApiCallChecker.hasError(answers))
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete resource definition: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx);
+        }
+    }
+
+    @Override
+    public void deleteAsync(
+        DataStore dataStore,
+        DataObject dataObject,
+        AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("deleteAsync: " + dataObject.getType() + ";" + dataObject.getId());
+        String errMsg = null;
+
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+
+        if (dataObject.getType() == DataObjectType.VOLUME)
+        {
+            VolumeInfo volumeInfo = (VolumeInfo) dataObject;
+
+            long storagePoolId = dataStore.getId();
+            final String rscName = "cs-" + volumeInfo.getPath();
+            deleteResourceDefinition(dataStore, rscName);
+
+            long usedBytes = storagePool.getUsedBytes();
+            long capacityIops = storagePool.getCapacityIops();
+
+            usedBytes -= volumeInfo.getSize();
+            if (volumeInfo.getMaxIops() != null)
+                capacityIops += volumeInfo.getMaxIops();
+
+            storagePool.setUsedBytes(Math.max(0, usedBytes));
+            storagePool.setCapacityIops(Math.max(0, capacityIops));
+
+            _storagePoolDao.update(storagePoolId, storagePool);
+        } else
+        {
+            errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync";
+        }
+
+        CommandResult result = new CommandResult();
+
+        result.setResult(errMsg);
+
+        callback.complete(result);
+    }
+
+    private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+
+        ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn();
+        final String rscName = "cs-" + vol.getUuid();
+        rscGrpSpawn.setResourceDefinitionName(rscName);
+        rscGrpSpawn.addVolumeSizesItem(vol.getSize() / 1024);
+
+        try
+        {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = linstorApi.resourceGroupSpawn(rscGrp, rscGrpSpawn);
+            if (ApiCallChecker.hasError(answers))
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to spawn resource.");
+            }
+
+            List<ResourceWithVolumes> resources = linstorApi.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty())
+            {
+                s_logger.info("Linstor: Created drbd device: " + resources.get(0).getVolumes().get(0).getDevicePath());
+                return resources.get(0).getVolumes().get(0).getDevicePath();
+            } else
+            {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx);
+        }
+    }
+
+    @Override
+    public void createAsync(DataStore dataStore, DataObject vol, AsyncCompletionCallback<CreateCmdResult> callback)
+    {
+        String devPath = null;
+        String errMsg = null;
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+
+        try
+        {
+            switch (vol.getType())
+            {
+                case VOLUME:
+                    VolumeInfo volumeInfo = (VolumeInfo) vol;
+                    VolumeVO volume = _volumeDao.findById(volumeInfo.getId());
+                    s_logger.debug("createAsync - creating volume");
+                    devPath = createResource((VolumeInfo) vol, storagePool);
+                    volume.setFolder("/dev/");
+                    volume.setPoolId(storagePool.getId());
+                    volume.setUuid(vol.getUuid());
+                    volume.setPath(vol.getUuid());
+
+                    _volumeDao.update(volume.getId(), volume);
+                    break;
+                case SNAPSHOT:
+                    s_logger.debug("createAsync - creating snapshot");
+                    break;
+                case TEMPLATE:
+                    s_logger.debug("createAsync - creating template");
+                    break;

Review comment:
       If the driver doesn't create snapshot / template here, better do not handle these cases.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r626596844



##########
File path: plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java
##########
@@ -0,0 +1,195 @@
+package com.cloud.hypervisor.kvm.storage;
+
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.storage.Storage;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+
+public class LinstorStoragePool implements KVMStoragePool {
+    private final String _uuid;
+    private final String _sourceHost;
+    private final int _sourcePort;
+    private final Storage.StoragePoolType _storagePoolType;
+    private final StorageAdaptor _storageAdaptor;
+    private final String _resourceGroup;
+
+    public LinstorStoragePool(
+            String uuid,
+            String host,
+            int port,
+            String resourceGroup,
+            Storage.StoragePoolType storagePoolType,
+            StorageAdaptor storageAdaptor) {
+        _uuid = uuid;
+        _sourceHost = host;
+        _sourcePort = port;
+        _storagePoolType = storagePoolType;
+        _storageAdaptor = storageAdaptor;
+        _resourceGroup = resourceGroup;
+    }
+
+    @Override
+    public KVMPhysicalDisk createPhysicalDisk(
+        String name,
+        QemuImg.PhysicalDiskFormat format,
+        Storage.ProvisioningType provisioningType,
+        long size)

Review comment:
       Do you have an exact formatting guideline somewhere?
   I tried to bring them a bit more in line now




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-831969863


   Yes, but I'm also open for design discussions as most of the code was done by looking at other storage plugins and trying to understand(debug) how cloudstack handles most things.
   So if there are stupid or unnecessary things in my code please don't be a afraid to point me into the right directions.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] sureshanaparti commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
sureshanaparti commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-880729143


   > @rhtyd
   > 
   > > For example, you can run and share results of your smoketests in your env (or using virtualised https://github.com/shapeblue/mbx) with Linstor storage with KVM as the only primary storage. Or a set of manual tests. We can merge based on your provided test results and regression smoketests, and after all review items are addressed.
   > 
   > So I'm in the middle of setup a new cluster with just Linstor as primary storage, and in the setup wizard I noticed that only the options for (nfs, SharedMountPoint, RBD, CLVM, Gluster). So why are here solidfire, datera and others missing and is it ok to add the default nfs primary storage and afteward deleted it and add just a Linstor one? Or should I be able to already pick Linstor here?
   
   @rp- The protocol while adding primary storage from the UI, is basically used to build the url of the storage pool.
   
   Protocol:
   <img width="388" alt="Protocol_AddPrimaryStorage" src="https://user-images.githubusercontent.com/12028987/125801847-47e25bb2-bf7f-4272-b5e3-e589b40f899e.png">
   
   Provider:
   <img width="389" alt="Provider_AddPrimaryStorage" src="https://user-images.githubusercontent.com/12028987/125801880-ecfb8d40-e619-4257-b1d1-4dbc532efcee.png">
   
   In your case, you can either use a new protocol 'Linstor', Otherwise use 'custom' protocol and make sure the 'Linstor' provider is selected, to general the url for the storage pool and pass to createStoragePool API.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] nvazquez commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
nvazquez commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-920055532


   Thanks @rp- @rhtyd - smoke tests passed, let's wait for @sureshanaparti's review


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-919849180


   Thanks @rp- 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-914112060


   @blueorangutan package


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r626596161



##########
File path: plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
##########
@@ -0,0 +1,420 @@
+// 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.driver;
+
+import com.linbit.linstor.api.ApiCallChecker;
+import com.linbit.linstor.api.ApiException;
+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.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
+import com.linbit.linstor.api.model.VolumeDefinitionModify;
+
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.to.DataObjectType;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.host.Host;
+import com.cloud.storage.ResizeVolumePayload;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
+import org.apache.cloudstack.storage.volume.VolumeObject;
+import org.apache.log4j.Logger;
+
+public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver {
+    private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class);
+    @Inject
+    private PrimaryDataStoreDao _storagePoolDao;
+    @Inject
+    private VolumeDao _volumeDao;
+
+    public LinstorPrimaryDataStoreDriverImpl()
+    {
+    }
+
+    @Override
+    public Map<String, String> getCapabilities()
+    {
+        Map<String, String> mapCapabilities = new HashMap<>();
+
+        // Linstor can create a volume from a volume via snapshot restore, but this would only work
+        // if a LVM-thin storage pool is used, TBH it would also work with ZFS pools, but there you always
+        // have to keep in mind the child-parent snapshot relationship
+        // So we will rely on a plain copy volume mechanism which is more robust for now
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString(), Boolean.FALSE.toString());
+
+        // for now, technically possible
+        mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.FALSE.toString());
+        // CAN_CREATE_VOLUME_FROM_SNAPSHOT see note from CAN_CREATE_VOLUME_FROM_VOLUME
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.FALSE.toString());
+        mapCapabilities.put(DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString(), Boolean.FALSE.toString());
+
+        mapCapabilities.put(DataStoreCapabilities.VOLUME_SNAPSHOT_QUIESCEVM.toString(), Boolean.FALSE.toString());
+
+        return mapCapabilities;
+    }
+
+    @Override
+    public DataTO getTO(DataObject data)
+    {
+        return null;
+    }
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store)
+    {
+        return null;
+    }
+
+    @Override
+    public ChapInfo getChapInfo(DataObject dataObject)
+    {
+        return null;
+    }
+
+    @Override
+    public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+        return false;
+    }
+
+    @Override
+    public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+    }
+
+    @Override
+    public long getUsedBytes(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getUsedIops(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool)
+    {
+        return dataObject.getSize();
+    }
+
+    @Override
+    public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    private void deleteResourceDefinition(DataStore dataStore, String rscDefName)
+    {
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceDefinitionDelete(rscDefName);
+            if (ApiCallChecker.hasError(answers))
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete resource definition: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx);
+        }
+    }
+
+    @Override
+    public void deleteAsync(
+        DataStore dataStore,
+        DataObject dataObject,
+        AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("deleteAsync: " + dataObject.getType() + ";" + dataObject.getId());
+        String errMsg = null;
+
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+
+        if (dataObject.getType() == DataObjectType.VOLUME)
+        {
+            VolumeInfo volumeInfo = (VolumeInfo) dataObject;
+
+            long storagePoolId = dataStore.getId();
+            final String rscName = "cs-" + volumeInfo.getPath();
+            deleteResourceDefinition(dataStore, rscName);
+
+            long usedBytes = storagePool.getUsedBytes();
+            long capacityIops = storagePool.getCapacityIops();
+
+            usedBytes -= volumeInfo.getSize();
+            if (volumeInfo.getMaxIops() != null)
+                capacityIops += volumeInfo.getMaxIops();
+
+            storagePool.setUsedBytes(Math.max(0, usedBytes));
+            storagePool.setCapacityIops(Math.max(0, capacityIops));
+
+            _storagePoolDao.update(storagePoolId, storagePool);
+        } else
+        {
+            errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync";
+        }
+
+        CommandResult result = new CommandResult();
+
+        result.setResult(errMsg);
+
+        callback.complete(result);
+    }
+
+    private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+
+        ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn();
+        final String rscName = "cs-" + vol.getUuid();
+        rscGrpSpawn.setResourceDefinitionName(rscName);
+        rscGrpSpawn.addVolumeSizesItem(vol.getSize() / 1024);
+
+        try
+        {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = linstorApi.resourceGroupSpawn(rscGrp, rscGrpSpawn);
+            if (ApiCallChecker.hasError(answers))
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to spawn resource.");
+            }
+
+            List<ResourceWithVolumes> resources = linstorApi.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty())
+            {
+                s_logger.info("Linstor: Created drbd device: " + resources.get(0).getVolumes().get(0).getDevicePath());
+                return resources.get(0).getVolumes().get(0).getDevicePath();
+            } else
+            {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx);
+        }
+    }
+
+    @Override
+    public void createAsync(DataStore dataStore, DataObject vol, AsyncCompletionCallback<CreateCmdResult> callback)
+    {
+        String devPath = null;
+        String errMsg = null;
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+
+        try
+        {
+            switch (vol.getType())
+            {
+                case VOLUME:
+                    VolumeInfo volumeInfo = (VolumeInfo) vol;
+                    VolumeVO volume = _volumeDao.findById(volumeInfo.getId());
+                    s_logger.debug("createAsync - creating volume");
+                    devPath = createResource((VolumeInfo) vol, storagePool);
+                    volume.setFolder("/dev/");
+                    volume.setPoolId(storagePool.getId());
+                    volume.setUuid(vol.getUuid());
+                    volume.setPath(vol.getUuid());
+
+                    _volumeDao.update(volume.getId(), volume);
+                    break;
+                case SNAPSHOT:
+                    s_logger.debug("createAsync - creating snapshot");
+                    break;
+                case TEMPLATE:
+                    s_logger.debug("createAsync - creating template");
+                    break;

Review comment:
       Hmm I wouldn't completely remove the cases, but I added an errMsg, so the callback gets an error answer,
   If I would remove the cases, it would return an InvalidDataobject error, which it isn't




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-870344074


   Ping @rp- any update for us - is this ready for review/testing, and functionally complete from your side?
   @blueorangutan package


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-956571240


   @rp- @Philipp-Reisner can you check a potential Linstor regression - https://markmail.org/message/tc4qkm2izzcjqahn 
   I'll tag/cc you in the email thread too.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] nvazquez commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
nvazquez commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-889558744


   Thanks for the update @rp- 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] slavkap commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
slavkap commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r701831273



##########
File path: plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
##########
@@ -0,0 +1,720 @@
+// 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.driver;
+
+import com.linbit.linstor.api.ApiException;
+import com.linbit.linstor.api.CloneWaiter;
+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.ResourceDefinition;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted;
+import com.linbit.linstor.api.model.ResourceDefinitionCreate;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+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.VolumeDefinitionModify;
+
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.agent.api.to.DiskTO;
+import com.cloud.host.Host;
+import com.cloud.storage.ResizeVolumePayload;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.VolumeDetailVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.SnapshotDetailsDao;
+import com.cloud.storage.dao.SnapshotDetailsVO;
+import com.cloud.storage.dao.VMTemplatePoolDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.storage.command.CreateObjectAnswer;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
+import org.apache.cloudstack.storage.volume.VolumeObject;
+import org.apache.log4j.Logger;
+
+public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver {
+    private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class);
+    @Inject private PrimaryDataStoreDao _storagePoolDao;
+    @Inject private VolumeDao _volumeDao;
+    @Inject private VolumeDetailsDao _volumeDetailsDao;
+    @Inject private VMTemplatePoolDao _vmTemplatePoolDao;
+    @Inject private SnapshotDao _snapshotDao;
+    @Inject private SnapshotDetailsDao _snapshotDetailsDao;
+
+    public LinstorPrimaryDataStoreDriverImpl()
+    {
+    }
+
+    @Override
+    public Map<String, String> getCapabilities()
+    {
+        Map<String, String> mapCapabilities = new HashMap<>();
+
+        // Linstor will be restricted to only run on LVM-THIN and ZFS storage pools with ACS
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString(), Boolean.TRUE.toString());
+
+        // fetch if lvm-thin or ZFS
+        mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        // CAN_CREATE_VOLUME_FROM_SNAPSHOT see note from CAN_CREATE_VOLUME_FROM_VOLUME
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+        mapCapabilities.put(DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        return mapCapabilities;
+    }
+
+    @Override
+    public DataTO getTO(DataObject data)
+    {
+        return null;
+    }
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store)
+    {
+        return null;
+    }
+
+    @Override
+    public ChapInfo getChapInfo(DataObject dataObject)
+    {
+        return null;
+    }
+
+    @Override
+    public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+        return false;
+    }
+
+    @Override
+    public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+    }
+
+    @Override
+    public long getUsedBytes(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getUsedIops(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool)
+    {
+        return dataObject.getSize();
+    }
+
+    @Override
+    public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    private String getSnapshotName(String snapshotUuid) {
+        return LinstorUtil.RSC_PREFIX + snapshotUuid;
+    }
+
+    private void deleteResourceDefinition(StoragePoolVO storagePoolVO, String rscDefName)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceDefinitionDelete(rscDefName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete resource definition: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private void deleteSnapshot(@Nonnull DataStore dataStore, @Nonnull String rscDefName, @Nonnull String snapshotName)
+    {
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceSnapshotDelete(rscDefName, snapshotName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete snapshot: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private long getCsIdForCloning(long volumeId, String cloneOf) {
+        VolumeDetailVO volumeDetail = _volumeDetailsDao.findDetail(volumeId, cloneOf);
+
+        if (volumeDetail != null && volumeDetail.getValue() != null) {
+            return Long.parseLong(volumeDetail.getValue());
+        }
+
+        return Long.MIN_VALUE;
+    }
+
+    @Override
+    public void deleteAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("deleteAsync: " + dataObject.getType() + ";" + dataObject.getUuid());
+        String errMsg = null;
+
+        final long storagePoolId = dataStore.getId();
+        final StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
+
+        switch (dataObject.getType()) {
+            case VOLUME:
+            {
+                final VolumeInfo volumeInfo = (VolumeInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getPath();
+                deleteResourceDefinition(storagePool, rscName);
+
+                long usedBytes = storagePool.getUsedBytes();
+                long capacityIops = storagePool.getCapacityIops();
+
+                usedBytes -= volumeInfo.getSize();
+                if (volumeInfo.getMaxIops() != null)
+                    capacityIops += volumeInfo.getMaxIops();
+
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                storagePool.setCapacityIops(Math.max(0, capacityIops));
+
+                _storagePoolDao.update(storagePoolId, storagePool);
+            }
+                break;
+            case SNAPSHOT:
+                final SnapshotInfo snapshotInfo = (SnapshotInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + snapshotInfo.getBaseVolume().getPath();
+                deleteSnapshot(dataStore, rscName, getSnapshotName(snapshotInfo.getUuid()));
+                long usedBytes = storagePool.getUsedBytes() - snapshotInfo.getSize();
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                _storagePoolDao.update(storagePoolId, storagePool);
+                break;
+            default:
+                errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync";
+                s_logger.error(errMsg);
+        }
+
+        if (callback != null) {
+            CommandResult result = new CommandResult();
+            result.setResult(errMsg);
+
+            callback.complete(result);
+        }
+    }
+
+    private void logLinstorAnswer(@Nonnull ApiCallRc answer) {
+        if (answer.isError()) {
+            s_logger.error(answer.getMessage());
+        } else if (answer.isWarning()) {
+            s_logger.warn(answer.getMessage());
+        } else if (answer.isInfo()) {
+            s_logger.info(answer.getMessage());
+        }
+    }
+
+    private void logLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        answers.forEach(this::logLinstorAnswer);
+    }
+
+    private void checkLinstorAnswersThrow(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        if (answers.hasError())
+        {
+            String errMsg = answers.stream()
+                .filter(ApiCallRc::isError)
+                .findFirst()
+                .map(ApiCallRc::getMessage).orElse("Unknown linstor error");
+            throw new CloudRuntimeException(errMsg);
+        }
+    }
+
+    private String checkLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        return answers.stream().filter(ApiCallRc::isError).findFirst().map(ApiCallRc::getMessage).orElse(null);
+    }
+
+    private String getDeviceName(DevelopersApi linstorApi, String rscName) throws ApiException {
+        List<ResourceWithVolumes> resources = linstorApi.viewResources(
+            Collections.emptyList(),
+            Collections.singletonList(rscName),
+            Collections.emptyList(),
+            null,
+            null,
+            null);
+        if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty())
+        {
+            s_logger.info("Linstor: Created drbd device: " + resources.get(0).getVolumes().get(0).getDevicePath());
+            return resources.get(0).getVolumes().get(0).getDevicePath();
+        } else
+        {
+            s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+            throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+        }
+    }
+
+    private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+
+        ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn();
+        final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
+        rscGrpSpawn.setResourceDefinitionName(rscName);
+        rscGrpSpawn.addVolumeSizesItem(vol.getSize() / 1024);
+
+        try
+        {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = linstorApi.resourceGroupSpawn(rscGrp, rscGrpSpawn);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String cloneResource(long csCloneId, VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        // get the cached template on this storage
+        VMTemplateStoragePoolVO tmplPoolRef = _vmTemplatePoolDao.findByPoolTemplate(
+            storagePoolVO.getId(), csCloneId, null);
+
+        if (tmplPoolRef != null) {
+            final String cloneRes = LinstorUtil.RSC_PREFIX + tmplPoolRef.getLocalDownloadPath();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+            try {
+                s_logger.debug("Clone resource definition " + cloneRes + " to " + rscName);
+                ResourceDefinitionCloneRequest cloneRequest = new ResourceDefinitionCloneRequest();
+                cloneRequest.setName(rscName);
+                ResourceDefinitionCloneStarted cloneStarted = linstorApi.resourceDefinitionClone(
+                    cloneRes, cloneRequest);
+
+                checkLinstorAnswersThrow(cloneStarted.getMessages());
+
+                if (!CloneWaiter.waitFor(linstorApi, cloneStarted)) {
+                    throw new CloudRuntimeException("Clone for resource " + rscName + " failed.");
+                }
+
+                return getDeviceName(linstorApi, rscName);
+            } catch (ApiException apiEx) {
+                s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+                throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+            }
+        } else {
+            throw new CloudRuntimeException(
+                "Unable to find Linstor resource for the following template data-object ID: " + csCloneId);
+        }
+    }
+
+    private String createResourceFromSnapshot(long csSnapshotId, String rscName, StoragePoolVO storagePoolVO) {
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+        final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        SnapshotVO snapshotVO = _snapshotDao.findById(csSnapshotId);
+        String snapName = LinstorUtil.RSC_PREFIX + snapshotVO.getUuid();
+        VolumeVO volumeVO = _volumeDao.findById(snapshotVO.getVolumeId());
+        String cloneRes = LinstorUtil.RSC_PREFIX + volumeVO.getPath();
+
+        try
+        {
+            s_logger.debug("Create new resource definition: " + rscName);
+            ResourceDefinitionCreate rdCreate = new ResourceDefinitionCreate();
+            ResourceDefinition rd = new ResourceDefinition();
+            rd.setName(rscName);
+            rd.setResourceGroupName(rscGrp);
+            rdCreate.setResourceDefinition(rd);
+            ApiCallRcList answers = linstorApi.resourceDefinitionCreate(rdCreate);
+            checkLinstorAnswersThrow(answers);
+
+            SnapshotRestore snapshotRestore = new SnapshotRestore();
+            snapshotRestore.toResource(rscName);
+
+            s_logger.debug("Create new volume definition for snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotsRestoreVolumeDefinition(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            // restore snapshot to new resource
+            s_logger.debug("Restore resource from snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotRestore(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String createVolume(VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = getCsIdForCloning(volumeInfo.getId(), "cloneOfSnapshot");
+        long csTemplateId = getCsIdForCloning(volumeInfo.getId(), "cloneOfTemplate");
+
+        if (csSnapshotId > 0) {
+            return createResourceFromSnapshot(csSnapshotId, LinstorUtil.RSC_PREFIX + volumeInfo.getUuid(), storagePoolVO);
+        } else if (csTemplateId > 0) {
+            return cloneResource(csTemplateId, volumeInfo, storagePoolVO);
+        } else {
+            return createResource(volumeInfo, storagePoolVO);
+        }
+    }
+
+    private void handleSnapshotDetails(long csSnapshotId, String name, String value) {
+        _snapshotDetailsDao.removeDetail(csSnapshotId, name);
+        SnapshotDetailsVO snapshotDetails = new SnapshotDetailsVO(csSnapshotId, name, value, false);
+        _snapshotDetailsDao.persist(snapshotDetails);
+    }
+
+    private void addTempVolumeToDb(long csSnapshotId, String tempVolumeName) {
+        // TEMP_VOLUME_ID is needed, to find which temporary resource should be deleted after copying it on agent side
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, LinstorUtil.RSC_PREFIX + tempVolumeName);
+        // the iqn will be used on the agent side to copy from, even though linstor doesn't have anything to do with IQN
+        handleSnapshotDetails(csSnapshotId, DiskTO.IQN, tempVolumeName);
+    }
+
+    private void removeTempVolumeFromDb(long csSnapshotId) {
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+        if (snapshotDetails == null || snapshotDetails.getValue() == null) {
+            throw new CloudRuntimeException(
+                "'removeTempVolumeId' should not be invoked unless " + LinstorUtil.TEMP_VOLUME_ID + " exists.");
+        }
+
+        String originalVolumeId = snapshotDetails.getValue();
+
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, originalVolumeId);
+
+        _snapshotDetailsDao.remove(snapshotDetails.getId());
+    }
+
+    private void createVolumeFromSnapshot(SnapshotInfo snapshotInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = snapshotInfo.getId();
+
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, "tempVolume");
+
+        if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("create"))
+        {
+            final String csName = "Temp-" + snapshotInfo.getUuid();
+            final String tempRscName = LinstorUtil.RSC_PREFIX + csName;
+            createResourceFromSnapshot(csSnapshotId, tempRscName, storagePoolVO);
+
+            s_logger.debug("Temp resource created: " + tempRscName);
+            addTempVolumeToDb(csSnapshotId, csName);
+        }
+        else if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("delete"))
+        {
+            snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+            deleteResourceDefinition(storagePoolVO, snapshotDetails.getValue());
+
+            s_logger.debug("Temp resource deleted: " + snapshotDetails.getValue());
+            removeTempVolumeFromDb(csSnapshotId);
+        }
+        else {
+            throw new CloudRuntimeException("Invalid state in 'createVolumeFromSnapshot(SnapshotInfo, StoragePoolVO)'");
+        }
+    }
+
+    @Override
+    public void createAsync(DataStore dataStore, DataObject vol, AsyncCompletionCallback<CreateCmdResult> callback)
+    {
+        String devPath = null;
+        String errMsg = null;
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+
+        try
+        {
+            switch (vol.getType())
+            {
+                case VOLUME:
+                    VolumeInfo volumeInfo = (VolumeInfo) vol;
+                    VolumeVO volume = _volumeDao.findById(volumeInfo.getId());
+                    s_logger.debug("createAsync - creating volume");
+                    devPath = createVolume(volumeInfo, storagePool);
+                    volume.setFolder("/dev/");
+                    volume.setPoolId(storagePool.getId());
+                    volume.setUuid(vol.getUuid());
+                    volume.setPath(vol.getUuid());
+
+                    _volumeDao.update(volume.getId(), volume);
+                    break;
+                case SNAPSHOT:
+                    s_logger.debug("createAsync - SNAPSHOT");
+                    createVolumeFromSnapshot((SnapshotInfo) vol, storagePool);
+                    break;
+                case TEMPLATE:
+                    errMsg = "creating template - not supported";
+                    s_logger.error("createAsync - " + errMsg);
+                    break;
+                default:
+                    errMsg = "Invalid DataObjectType (" + vol.getType() + ") passed to createAsync";
+                    s_logger.error(errMsg);
+            }
+        } catch (Exception ex)
+        {
+            errMsg = ex.getMessage();
+
+            s_logger.error("createAsync: " + errMsg);
+            if (callback == null)
+            {
+                throw ex;
+            }
+        }
+
+        if (callback != null)
+        {
+            CreateCmdResult result = new CreateCmdResult(devPath, new Answer(null, errMsg == null, errMsg));
+            result.setResult(errMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public void revertSnapshot(
+        SnapshotInfo snapshot,
+        SnapshotInfo snapshotOnPrimaryStore,
+        AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("Linstor: revertSnapshot");
+        final VolumeInfo volumeInfo = snapshot.getBaseVolume();
+        VolumeVO volumeVO = _volumeDao.findById(volumeInfo.getId());
+        if (volumeVO == null || volumeVO.getRemoved() != null) {
+            CommandResult commandResult = new CommandResult();
+            commandResult.setResult("The volume that the snapshot belongs to no longer exists.");
+            callback.complete(commandResult);
+            return;
+        }
+
+        String resultMsg;
+        try {
+            final StoragePool pool = (StoragePool) snapshot.getDataStore();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final String snapName = LinstorUtil.RSC_PREFIX + snapshot.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(pool.getHostAddress());
+
+            ApiCallRcList answers = linstorApi.resourceSnapshotRollback(rscName, snapName);
+            resultMsg = checkLinstorAnswers(answers);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            resultMsg = apiEx.getBestMessage();
+        }
+
+        if (callback != null)
+        {
+            CommandResult result = new CommandResult();
+            result.setResult(resultMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public boolean canCopy(DataObject srcData, DataObject destData)
+    {
+        return false;
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback)

Review comment:
       @rp- I'm not sure if I'm right, but when you do a live resize, you're resizing the volume only on Linstor, but the guest/hypervisor is not informed. That's because the Linstor primary storage is not managed, and you have to notify qemu from here. Please excuse me if I'm wrong




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909255795


   <b>Trillian Build Failed (tid-1863)<b/>


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909174240






-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909202068


   I have ran the test from `test/integration/plugins/linstor/test_linstor_volumes.py` and it succeeded and did some manual setup with a linstor only storage pool and creating a VM in there(+ the system VM's).
   I'll try to get additional testing done in the next week.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-832016828


   @rhtyd a Trillian-Jenkins test job (centos7 mgmt + kvm-centos7) has been kicked to run smoke tests


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-880969322


   <b>Trillian test result (tid-1275)</b>
   Environment: kvm-centos7 (x2), Advanced Networking with Mgmt server 7
   Total time taken: 43680 seconds
   Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr4994-t1275-kvm-centos7.zip
   Intermittent failure detected: /marvin/tests/smoke/test_deploy_virtio_scsi_vm.py
   Intermittent failure detected: /marvin/tests/smoke/test_privategw_acl.py
   Intermittent failure detected: /marvin/tests/smoke/test_routers_network_ops.py
   Intermittent failure detected: /marvin/tests/smoke/test_vpc_redundant.py
   Smoke tests completed. 86 look OK, 2 have error(s)
   Only failed tests results shown below:
   
   
   Test | Result | Time (s) | Test File
   --- | --- | --- | ---
   test_03_vpc_privategw_restart_vpc_cleanup | `Failure` | 399.55 | test_privategw_acl.py
   test_01_create_redundant_VPC_2tiers_4VMs_4IPs_4PF_ACL | `Failure` | 961.85 | test_vpc_redundant.py
   test_03_create_redundant_VPC_1tier_2VMs_2IPs_2PF_ACL_reboot_routers | `Failure` | 476.06 | test_vpc_redundant.py
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-915830436


   @nvazquez I did rebase against current main, maybe retry the builds, otherwise I would need some error logs


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] sureshanaparti commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
sureshanaparti commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r626391751



##########
File path: plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java
##########
@@ -0,0 +1,327 @@
+/*
+ * 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.lifecycle;
+
+import javax.inject.Inject;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import com.cloud.agent.AgentManager;
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.CreateStoragePoolCommand;
+import com.cloud.agent.api.StoragePoolInfo;
+import com.cloud.capacity.CapacityManager;
+import com.cloud.dc.ClusterVO;
+import com.cloud.dc.dao.ClusterDao;
+import com.cloud.host.Host;
+import com.cloud.host.HostVO;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.resource.ResourceManager;
+import com.cloud.storage.Storage;
+import com.cloud.storage.StorageManager;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.StoragePoolAutomation;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCycle;
+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.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
+import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper;
+import org.apache.log4j.Logger;
+
+public class LinstorPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle {
+    private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreLifeCycleImpl.class);
+
+    @Inject
+    private ClusterDao clusterDao;
+    @Inject
+    PrimaryDataStoreDao _primaryDataStoreDao;
+    @Inject
+    private ResourceManager resourceMgr;
+    @Inject
+    private StorageManager _storageMgr;
+    @Inject
+    PrimaryDataStoreHelper dataStoreHelper;
+    @Inject
+    private StoragePoolAutomation storagePoolAutomation;
+    @Inject
+    private CapacityManager _capacityMgr;
+    @Inject
+    AgentManager _agentMgr;
+
+    public LinstorPrimaryDataStoreLifeCycleImpl()
+    {
+    }
+
+    private static boolean isSupportedHypervisorType(HypervisorType hypervisorType) {
+        return HypervisorType.KVM.equals(hypervisorType);
+    }
+
+    @Override
+    public DataStore initialize(Map<String, Object> dsInfos) {
+        String url = (String) dsInfos.get("url");
+        Long zoneId = (Long) dsInfos.get("zoneId");
+        Long podId = (Long) dsInfos.get("podId");
+        Long clusterId = (Long) dsInfos.get("clusterId");
+        String storagePoolName = (String) dsInfos.get("name");
+        String providerName = (String) dsInfos.get("providerName");
+        String tags = (String) dsInfos.get("tags");
+        @SuppressWarnings("unchecked")
+        Map<String, String> details = (Map<String, String>) dsInfos.get("details");
+
+        final String resourceGroup = details.get(LinstorUtil.RSC_GROUP);
+
+        final String uuid = UUID.randomUUID().toString();
+
+        PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters();
+
+        // checks if primary datastore is clusterwide. If so, uses the clusterId to set
+        // the uuid and then sets the podId and clusterId parameters
+        if (clusterId != null) {
+            if (podId == null) {
+                throw new CloudRuntimeException("The Pod ID must be specified.");
+            }
+            if (zoneId == null) {
+                throw new CloudRuntimeException("The Zone ID must be specified.");
+            }
+            ClusterVO cluster = clusterDao.findById(clusterId);
+            s_logger.info("Linstor: Setting Linstor cluster-wide primary storage uuid to " + uuid);
+            parameters.setPodId(podId);
+            parameters.setClusterId(clusterId);
+
+            HypervisorType hypervisorType = cluster.getHypervisorType();
+
+            if (!isSupportedHypervisorType(hypervisorType)) {
+                throw new CloudRuntimeException(hypervisorType + " is not a supported hypervisor type.");
+            }
+        }
+
+        if (!url.contains("://")) {
+            url = "http://" + url;
+        }
+
+        URL controllerURL;
+        int port = 3370;
+        try
+        {
+            controllerURL = new URL(url);
+            if (!controllerURL.getProtocol().startsWith("http")) {
+                throw new IllegalArgumentException("Linstor controller URL wrong protocol: " + url);
+            }
+            if (!controllerURL.getPath().isEmpty()) {
+                throw new IllegalArgumentException("Linstor controller URL shouldn't have a path: " + url);
+            }
+            if (controllerURL.getPort() == -1) {
+                port = controllerURL.getProtocol().equals("https") ? 3371 : 3370;
+                url += ":" + port;
+            }
+        } catch (MalformedURLException e)
+        {
+            throw new IllegalArgumentException("Linstor controller URL is not valid: " + e);
+        }
+
+        long capacityBytes = LinstorUtil.getCapacityBytes(url, resourceGroup);
+
+        if (capacityBytes <= 0) {
+            throw new IllegalArgumentException("'capacityBytes' must be present and greater than 0.");
+        }
+
+        parameters.setHost(url);
+        parameters.setPort(port);
+        parameters.setPath(resourceGroup);
+        parameters.setType(Storage.StoragePoolType.Linstor);
+        parameters.setUuid(uuid);
+        parameters.setZoneId(zoneId);
+        parameters.setName(storagePoolName);
+        parameters.setProviderName(providerName);
+        parameters.setManaged(false);
+        parameters.setCapacityBytes(capacityBytes);
+        parameters.setUsedBytes(0);
+        parameters.setCapacityIops(0L);
+        parameters.setHypervisorType(HypervisorType.KVM);
+        parameters.setTags(tags);
+        parameters.setDetails(details);
+        parameters.setUserInfo(resourceGroup);
+
+        return dataStoreHelper.createPrimaryDataStore(parameters);
+    }
+
+    protected boolean createStoragePool(long hostId, StoragePool pool) {
+        s_logger.debug("creating pool " + pool.getName() + " on  host " + hostId);
+
+        if (pool.getPoolType() != Storage.StoragePoolType.Linstor) {
+            s_logger.warn(" Doesn't support storage pool type " + pool.getPoolType());
+            return false;
+        }
+        CreateStoragePoolCommand cmd = new CreateStoragePoolCommand(true, pool);
+        final Answer answer = _agentMgr.easySend(hostId, cmd);
+        if (answer != null && answer.getResult()) {
+            return true;
+        } else {
+            _primaryDataStoreDao.expunge(pool.getId());
+            String msg = answer != null ?
+                "Can not create storage pool through host " + hostId + " due to " + answer.getDetails() :
+                "Can not create storage pool through host " + hostId + " due to CreateStoragePoolCommand returns null";
+            s_logger.warn(msg);
+            throw new CloudRuntimeException(msg);
+        }
+    }
+
+    @Override
+    public boolean attachCluster(DataStore dataStore, ClusterScope scope) {
+        PrimaryDataStoreInfo primaryDataStoreInfo = (PrimaryDataStoreInfo) dataStore;
+
+        // check if there is at least one host up in this cluster
+        List<HostVO> allHosts = resourceMgr.listAllUpAndEnabledHosts(Host.Type.Routing,
+            primaryDataStoreInfo.getClusterId(), primaryDataStoreInfo.getPodId(),
+            primaryDataStoreInfo.getDataCenterId());
+
+        if (allHosts.isEmpty()) {
+            _primaryDataStoreDao.expunge(primaryDataStoreInfo.getId());
+
+            throw new CloudRuntimeException(
+                "No host up to associate a storage pool with in cluster " + primaryDataStoreInfo.getClusterId());
+        }
+
+        List<HostVO> poolHosts = new ArrayList<>();
+        for (HostVO host : allHosts) {
+            try {
+                createStoragePool(host.getId(), primaryDataStoreInfo);
+
+                _storageMgr.connectHostToSharedPool(host.getId(), primaryDataStoreInfo.getId());
+
+                poolHosts.add(host);
+            } catch (Exception e) {
+                s_logger.warn("Unable to establish a connection between " + host + " and " + primaryDataStoreInfo, e);
+            }
+        }
+
+        if (poolHosts.isEmpty()) {
+            s_logger.warn("No host can access storage pool '" + primaryDataStoreInfo + "' on cluster '"
+                + primaryDataStoreInfo.getClusterId() + "'.");
+
+            _primaryDataStoreDao.expunge(primaryDataStoreInfo.getId());
+
+            throw new CloudRuntimeException("Failed to access storage pool");
+        }
+
+        dataStoreHelper.attachCluster(dataStore);
+
+        return true;
+    }
+
+    @Override
+    public boolean attachZone(DataStore dataStore, ZoneScope scope, HypervisorType hypervisorType) {
+        List<HostVO> hosts = resourceMgr.listAllUpAndEnabledHostsInOneZoneByHypervisor(HypervisorType.KVM,

Review comment:
       Hypervisor hard-coded to KVM here, check with the _hypervisorType_ input and if it is supported or not. 




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-870394110


   Packaging result: :heavy_check_mark: centos7 :heavy_multiplication_x: centos8 :heavy_check_mark: debian. SL-JID 408


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-845882655


   @rp- here are some links for you for learning/references:  cc @Philipp-Reisner
   https://github.com/shapeblue/hackerbook
   https://github.com/shapeblue/mbx
   
   Here are some high-level lifecycle operations a typical storage provider can support: (it also depends if which ones of the following operations are even possible/allowed by the storage control plane)
   - Volumes: list (by name, id etc), create, resize, clone, delete, migrate, map/unmap/check-map (to a host), download volume (via secondary storage), get volume/disk statistics/metrics (usage, iops etc)
   - Volume Snapshots: list, take snapshot, revert snapshot, delete (note: this is a single volume/disk), download snapshot (via secondary storage) 
   - VM Snapshots: list, take snapshot, revert snapshot, delete (note: this may require a consistency group, i.e. a group of volumes/disks that are attached to a disk with or without memory, i.e. disk-only VM snapshot and disk+ram snapshots)
   - Template: create template from snapshot, create template from volume/disk
   - Storage pool (or Primary storage): list (by name, id etc), get pool statistics/metrics (usage, iops etc), create/add (with label)
   - Migration: can we migrate volumes from one storage pool (Linstore resource pool) to another pool (Linstore resource pool or other types of pool), can we migrate VM across hosts/clusters with/without Linstor storage
   
   Most of the above operations already have a high level CloudStack API, generally the storage provider/plugin implements the backend for these operations. In some cases, if an operation is not allowed (for example, raw-disk snapshots on KVM) you may need to add conditional checks or refactor the CloudStack service layers (API, managers, storage sub-system... which largely defines the policy) and hypervisor sub-system (KVM+storage specific storage data motion strategy, LibvirtStoragePoolDef, handling of config drive, VM/disk migration, KVM specific linstore storage pool manager + storage processor + storage adaptor classes, i.e. they implement how storage/hypervisor specific behaviour are executed).
   
   In general you would setup a dev-test env, and while adding new storage pool in CloudStack add a label to that pool (primary storage) and then create disk offerings with the label - this way you can force VM's root&data disks to be created on the pool. These are called storage tags (we've something similar called host tags which can be used to map compute/service offerings with hypervisor hosts). Lastly, you want to write Marvin-based integration tests specific to your storage provider.
   
   Example implementations for references:
   https://github.com/apache/cloudstack/pull/4304 (ScaleIO storage provider)
   https://github.com/apache/cloudstack/tree/master/plugins/storage/volume (all current storage providers)
   https://github.com/apache/cloudstack/tree/master/test/integration/plugins (examples of plugin-specific integration tests)
   https://cwiki.apache.org/confluence/display/CLOUDSTACK/Storage+subsystem+2.0 (CloudStack storage framework)
   
   Hope this helps.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-899438325


   @andrijapanicsb @rhtyd
   
   So I was able to finish the "Clone" feature in Linstor and continued to integrate this new functionality into the Linstor volume plugin. But while testing I encountered the following problem and I'm not sure who has to handle it(or how):
   
   I tried to setup a cluster with just the Linstor primary storage and my problem is now the format of the system-vm image.
   For kvm it is in the qcow2 format and as other storage providers Linstor will report RAW as supported and used format.
   But what I end up with is a qcow2 file stored within our block device(which isn't really that much of a problem) but libvirt will see the disk as RAW and will of course fail to boot the qcow2 image.
   
   So either the image gets converted while creating the first(hidden) base volume or I would need to tell libvirt somewhere that this is indeed a qcow2 image.
   
   Do other storage plugins handle this correctly? Like RDB, Powerflex...? As I saw in the code they also only handle RAW format.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-914112701


   @rhtyd a Jenkins job has been kicked to build packages. I'll keep you posted as I make progress.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-957147583


   @nvazquez I'm on it via a different communication channel
   
   But I suspect a configuration problem, I'll report back here what solved the case.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-919769047


   @blueorangutan test centos7 vmware-67u3
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909204154


   <b>Trillian Build Failed (tid-1858)<b/>


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-880456193


   @rp- I would try and add Linstor in the list (API/UI) so you don't need to do the hack. As a user with your feature I should we able to simply select and use Linstor.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909174240






-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-870419182


   @slavkap <3 That was it, thank you! Just weird that there is nowhere any SQL exception or anything similar...


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] slavkap commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
slavkap commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r704317430



##########
File path: plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
##########
@@ -0,0 +1,720 @@
+// 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.driver;
+
+import com.linbit.linstor.api.ApiException;
+import com.linbit.linstor.api.CloneWaiter;
+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.ResourceDefinition;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted;
+import com.linbit.linstor.api.model.ResourceDefinitionCreate;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+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.VolumeDefinitionModify;
+
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.agent.api.to.DiskTO;
+import com.cloud.host.Host;
+import com.cloud.storage.ResizeVolumePayload;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.VolumeDetailVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.SnapshotDetailsDao;
+import com.cloud.storage.dao.SnapshotDetailsVO;
+import com.cloud.storage.dao.VMTemplatePoolDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.storage.command.CreateObjectAnswer;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
+import org.apache.cloudstack.storage.volume.VolumeObject;
+import org.apache.log4j.Logger;
+
+public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver {
+    private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class);
+    @Inject private PrimaryDataStoreDao _storagePoolDao;
+    @Inject private VolumeDao _volumeDao;
+    @Inject private VolumeDetailsDao _volumeDetailsDao;
+    @Inject private VMTemplatePoolDao _vmTemplatePoolDao;
+    @Inject private SnapshotDao _snapshotDao;
+    @Inject private SnapshotDetailsDao _snapshotDetailsDao;
+
+    public LinstorPrimaryDataStoreDriverImpl()
+    {
+    }
+
+    @Override
+    public Map<String, String> getCapabilities()
+    {
+        Map<String, String> mapCapabilities = new HashMap<>();
+
+        // Linstor will be restricted to only run on LVM-THIN and ZFS storage pools with ACS
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString(), Boolean.TRUE.toString());
+
+        // fetch if lvm-thin or ZFS
+        mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        // CAN_CREATE_VOLUME_FROM_SNAPSHOT see note from CAN_CREATE_VOLUME_FROM_VOLUME
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+        mapCapabilities.put(DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        return mapCapabilities;
+    }
+
+    @Override
+    public DataTO getTO(DataObject data)
+    {
+        return null;
+    }
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store)
+    {
+        return null;
+    }
+
+    @Override
+    public ChapInfo getChapInfo(DataObject dataObject)
+    {
+        return null;
+    }
+
+    @Override
+    public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+        return false;
+    }
+
+    @Override
+    public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+    }
+
+    @Override
+    public long getUsedBytes(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getUsedIops(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool)
+    {
+        return dataObject.getSize();
+    }
+
+    @Override
+    public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    private String getSnapshotName(String snapshotUuid) {
+        return LinstorUtil.RSC_PREFIX + snapshotUuid;
+    }
+
+    private void deleteResourceDefinition(StoragePoolVO storagePoolVO, String rscDefName)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceDefinitionDelete(rscDefName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete resource definition: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private void deleteSnapshot(@Nonnull DataStore dataStore, @Nonnull String rscDefName, @Nonnull String snapshotName)
+    {
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceSnapshotDelete(rscDefName, snapshotName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete snapshot: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private long getCsIdForCloning(long volumeId, String cloneOf) {
+        VolumeDetailVO volumeDetail = _volumeDetailsDao.findDetail(volumeId, cloneOf);
+
+        if (volumeDetail != null && volumeDetail.getValue() != null) {
+            return Long.parseLong(volumeDetail.getValue());
+        }
+
+        return Long.MIN_VALUE;
+    }
+
+    @Override
+    public void deleteAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("deleteAsync: " + dataObject.getType() + ";" + dataObject.getUuid());
+        String errMsg = null;
+
+        final long storagePoolId = dataStore.getId();
+        final StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
+
+        switch (dataObject.getType()) {
+            case VOLUME:
+            {
+                final VolumeInfo volumeInfo = (VolumeInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getPath();
+                deleteResourceDefinition(storagePool, rscName);
+
+                long usedBytes = storagePool.getUsedBytes();
+                long capacityIops = storagePool.getCapacityIops();
+
+                usedBytes -= volumeInfo.getSize();
+                if (volumeInfo.getMaxIops() != null)
+                    capacityIops += volumeInfo.getMaxIops();
+
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                storagePool.setCapacityIops(Math.max(0, capacityIops));
+
+                _storagePoolDao.update(storagePoolId, storagePool);
+            }
+                break;
+            case SNAPSHOT:
+                final SnapshotInfo snapshotInfo = (SnapshotInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + snapshotInfo.getBaseVolume().getPath();
+                deleteSnapshot(dataStore, rscName, getSnapshotName(snapshotInfo.getUuid()));
+                long usedBytes = storagePool.getUsedBytes() - snapshotInfo.getSize();
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                _storagePoolDao.update(storagePoolId, storagePool);
+                break;
+            default:
+                errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync";
+                s_logger.error(errMsg);
+        }
+
+        if (callback != null) {
+            CommandResult result = new CommandResult();
+            result.setResult(errMsg);
+
+            callback.complete(result);
+        }
+    }
+
+    private void logLinstorAnswer(@Nonnull ApiCallRc answer) {
+        if (answer.isError()) {
+            s_logger.error(answer.getMessage());
+        } else if (answer.isWarning()) {
+            s_logger.warn(answer.getMessage());
+        } else if (answer.isInfo()) {
+            s_logger.info(answer.getMessage());
+        }
+    }
+
+    private void logLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        answers.forEach(this::logLinstorAnswer);
+    }
+
+    private void checkLinstorAnswersThrow(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        if (answers.hasError())
+        {
+            String errMsg = answers.stream()
+                .filter(ApiCallRc::isError)
+                .findFirst()
+                .map(ApiCallRc::getMessage).orElse("Unknown linstor error");
+            throw new CloudRuntimeException(errMsg);
+        }
+    }
+
+    private String checkLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        return answers.stream().filter(ApiCallRc::isError).findFirst().map(ApiCallRc::getMessage).orElse(null);
+    }
+
+    private String getDeviceName(DevelopersApi linstorApi, String rscName) throws ApiException {
+        List<ResourceWithVolumes> resources = linstorApi.viewResources(
+            Collections.emptyList(),
+            Collections.singletonList(rscName),
+            Collections.emptyList(),
+            null,
+            null,
+            null);
+        if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty())
+        {
+            s_logger.info("Linstor: Created drbd device: " + resources.get(0).getVolumes().get(0).getDevicePath());
+            return resources.get(0).getVolumes().get(0).getDevicePath();
+        } else
+        {
+            s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+            throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+        }
+    }
+
+    private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+
+        ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn();
+        final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
+        rscGrpSpawn.setResourceDefinitionName(rscName);
+        rscGrpSpawn.addVolumeSizesItem(vol.getSize() / 1024);
+
+        try
+        {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = linstorApi.resourceGroupSpawn(rscGrp, rscGrpSpawn);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String cloneResource(long csCloneId, VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        // get the cached template on this storage
+        VMTemplateStoragePoolVO tmplPoolRef = _vmTemplatePoolDao.findByPoolTemplate(
+            storagePoolVO.getId(), csCloneId, null);
+
+        if (tmplPoolRef != null) {
+            final String cloneRes = LinstorUtil.RSC_PREFIX + tmplPoolRef.getLocalDownloadPath();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+            try {
+                s_logger.debug("Clone resource definition " + cloneRes + " to " + rscName);
+                ResourceDefinitionCloneRequest cloneRequest = new ResourceDefinitionCloneRequest();
+                cloneRequest.setName(rscName);
+                ResourceDefinitionCloneStarted cloneStarted = linstorApi.resourceDefinitionClone(
+                    cloneRes, cloneRequest);
+
+                checkLinstorAnswersThrow(cloneStarted.getMessages());
+
+                if (!CloneWaiter.waitFor(linstorApi, cloneStarted)) {
+                    throw new CloudRuntimeException("Clone for resource " + rscName + " failed.");
+                }
+
+                return getDeviceName(linstorApi, rscName);
+            } catch (ApiException apiEx) {
+                s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+                throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+            }
+        } else {
+            throw new CloudRuntimeException(
+                "Unable to find Linstor resource for the following template data-object ID: " + csCloneId);
+        }
+    }
+
+    private String createResourceFromSnapshot(long csSnapshotId, String rscName, StoragePoolVO storagePoolVO) {
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+        final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        SnapshotVO snapshotVO = _snapshotDao.findById(csSnapshotId);
+        String snapName = LinstorUtil.RSC_PREFIX + snapshotVO.getUuid();
+        VolumeVO volumeVO = _volumeDao.findById(snapshotVO.getVolumeId());
+        String cloneRes = LinstorUtil.RSC_PREFIX + volumeVO.getPath();
+
+        try
+        {
+            s_logger.debug("Create new resource definition: " + rscName);
+            ResourceDefinitionCreate rdCreate = new ResourceDefinitionCreate();
+            ResourceDefinition rd = new ResourceDefinition();
+            rd.setName(rscName);
+            rd.setResourceGroupName(rscGrp);
+            rdCreate.setResourceDefinition(rd);
+            ApiCallRcList answers = linstorApi.resourceDefinitionCreate(rdCreate);
+            checkLinstorAnswersThrow(answers);
+
+            SnapshotRestore snapshotRestore = new SnapshotRestore();
+            snapshotRestore.toResource(rscName);
+
+            s_logger.debug("Create new volume definition for snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotsRestoreVolumeDefinition(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            // restore snapshot to new resource
+            s_logger.debug("Restore resource from snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotRestore(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String createVolume(VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = getCsIdForCloning(volumeInfo.getId(), "cloneOfSnapshot");
+        long csTemplateId = getCsIdForCloning(volumeInfo.getId(), "cloneOfTemplate");
+
+        if (csSnapshotId > 0) {
+            return createResourceFromSnapshot(csSnapshotId, LinstorUtil.RSC_PREFIX + volumeInfo.getUuid(), storagePoolVO);
+        } else if (csTemplateId > 0) {
+            return cloneResource(csTemplateId, volumeInfo, storagePoolVO);
+        } else {
+            return createResource(volumeInfo, storagePoolVO);
+        }
+    }
+
+    private void handleSnapshotDetails(long csSnapshotId, String name, String value) {
+        _snapshotDetailsDao.removeDetail(csSnapshotId, name);
+        SnapshotDetailsVO snapshotDetails = new SnapshotDetailsVO(csSnapshotId, name, value, false);
+        _snapshotDetailsDao.persist(snapshotDetails);
+    }
+
+    private void addTempVolumeToDb(long csSnapshotId, String tempVolumeName) {
+        // TEMP_VOLUME_ID is needed, to find which temporary resource should be deleted after copying it on agent side
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, LinstorUtil.RSC_PREFIX + tempVolumeName);
+        // the iqn will be used on the agent side to copy from, even though linstor doesn't have anything to do with IQN
+        handleSnapshotDetails(csSnapshotId, DiskTO.IQN, tempVolumeName);
+    }
+
+    private void removeTempVolumeFromDb(long csSnapshotId) {
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+        if (snapshotDetails == null || snapshotDetails.getValue() == null) {
+            throw new CloudRuntimeException(
+                "'removeTempVolumeId' should not be invoked unless " + LinstorUtil.TEMP_VOLUME_ID + " exists.");
+        }
+
+        String originalVolumeId = snapshotDetails.getValue();
+
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, originalVolumeId);
+
+        _snapshotDetailsDao.remove(snapshotDetails.getId());
+    }
+
+    private void createVolumeFromSnapshot(SnapshotInfo snapshotInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = snapshotInfo.getId();
+
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, "tempVolume");
+
+        if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("create"))
+        {
+            final String csName = "Temp-" + snapshotInfo.getUuid();
+            final String tempRscName = LinstorUtil.RSC_PREFIX + csName;
+            createResourceFromSnapshot(csSnapshotId, tempRscName, storagePoolVO);
+
+            s_logger.debug("Temp resource created: " + tempRscName);
+            addTempVolumeToDb(csSnapshotId, csName);
+        }
+        else if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("delete"))
+        {
+            snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+            deleteResourceDefinition(storagePoolVO, snapshotDetails.getValue());
+
+            s_logger.debug("Temp resource deleted: " + snapshotDetails.getValue());
+            removeTempVolumeFromDb(csSnapshotId);
+        }
+        else {
+            throw new CloudRuntimeException("Invalid state in 'createVolumeFromSnapshot(SnapshotInfo, StoragePoolVO)'");
+        }
+    }
+
+    @Override
+    public void createAsync(DataStore dataStore, DataObject vol, AsyncCompletionCallback<CreateCmdResult> callback)
+    {
+        String devPath = null;
+        String errMsg = null;
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+
+        try
+        {
+            switch (vol.getType())
+            {
+                case VOLUME:
+                    VolumeInfo volumeInfo = (VolumeInfo) vol;
+                    VolumeVO volume = _volumeDao.findById(volumeInfo.getId());
+                    s_logger.debug("createAsync - creating volume");
+                    devPath = createVolume(volumeInfo, storagePool);
+                    volume.setFolder("/dev/");
+                    volume.setPoolId(storagePool.getId());
+                    volume.setUuid(vol.getUuid());
+                    volume.setPath(vol.getUuid());
+
+                    _volumeDao.update(volume.getId(), volume);
+                    break;
+                case SNAPSHOT:
+                    s_logger.debug("createAsync - SNAPSHOT");
+                    createVolumeFromSnapshot((SnapshotInfo) vol, storagePool);
+                    break;
+                case TEMPLATE:
+                    errMsg = "creating template - not supported";
+                    s_logger.error("createAsync - " + errMsg);
+                    break;
+                default:
+                    errMsg = "Invalid DataObjectType (" + vol.getType() + ") passed to createAsync";
+                    s_logger.error(errMsg);
+            }
+        } catch (Exception ex)
+        {
+            errMsg = ex.getMessage();
+
+            s_logger.error("createAsync: " + errMsg);
+            if (callback == null)
+            {
+                throw ex;
+            }
+        }
+
+        if (callback != null)
+        {
+            CreateCmdResult result = new CreateCmdResult(devPath, new Answer(null, errMsg == null, errMsg));
+            result.setResult(errMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public void revertSnapshot(
+        SnapshotInfo snapshot,
+        SnapshotInfo snapshotOnPrimaryStore,
+        AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("Linstor: revertSnapshot");
+        final VolumeInfo volumeInfo = snapshot.getBaseVolume();
+        VolumeVO volumeVO = _volumeDao.findById(volumeInfo.getId());
+        if (volumeVO == null || volumeVO.getRemoved() != null) {
+            CommandResult commandResult = new CommandResult();
+            commandResult.setResult("The volume that the snapshot belongs to no longer exists.");
+            callback.complete(commandResult);
+            return;
+        }
+
+        String resultMsg;
+        try {
+            final StoragePool pool = (StoragePool) snapshot.getDataStore();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final String snapName = LinstorUtil.RSC_PREFIX + snapshot.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(pool.getHostAddress());
+
+            ApiCallRcList answers = linstorApi.resourceSnapshotRollback(rscName, snapName);
+            resultMsg = checkLinstorAnswers(answers);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            resultMsg = apiEx.getBestMessage();
+        }
+
+        if (callback != null)
+        {
+            CommandResult result = new CommandResult();
+            result.setResult(resultMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public boolean canCopy(DataObject srcData, DataObject destData)
+    {
+        return false;
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback)

Review comment:
       Hmm, this tells that the notification of the guest was successful. Is there any change when you run:
   `virsh domblkinfo --device /dev/drbd1001 --domain i-2-253-VM --human`
   or
   `lsblk` from the guest?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-920587128


   Merged based on 2xlgtms, and on smoketests and Travis passing. We haven't tested Linstor itself which is tested by @rp- 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-919769309


   @rhtyd a Trillian-Jenkins test job (centos7 mgmt + vmware-67u3) has been kicked to run smoke tests


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] nvazquez commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
nvazquez commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-914802486


   Hi @rp- seems there is an issue on the packaging, can you please check?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] sureshanaparti commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
sureshanaparti commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-840540802


   > I added a basic PR for documentation and also a first version of the design document:
   > https://cwiki.apache.org/confluence/display/CLOUDSTACK/Linstor+storage+plugin
   > (sorry I mostly copied the stuff from the ScaleIO plugin)
   > 
   > Btw. do you have some storage test guidelines? Like what basic functions should work and how to test them?
   > I'm not to familiar with all Cloudstack functionalities, so chances are high that I simply missed some functionality tests.
   
   @rp- Please check if Linstor storage plugin supports all the functionalities mentioned in the cwiki. Can you add some storage references in cwiki ?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] GutoVeronezi commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
GutoVeronezi commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r629465841



##########
File path: plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
##########
@@ -0,0 +1,566 @@
+package com.cloud.hypervisor.kvm.storage;
+
+import com.linbit.linstor.api.ApiCallChecker;
+import com.linbit.linstor.api.ApiClient;
+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;
+import com.linbit.linstor.api.model.Properties;
+import com.linbit.linstor.api.model.ProviderKind;
+import com.linbit.linstor.api.model.Resource;
+import com.linbit.linstor.api.model.ResourceCreate;
+import com.linbit.linstor.api.model.ResourceDefinitionModify;
+import com.linbit.linstor.api.model.ResourceGroup;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
+import com.linbit.linstor.api.model.StoragePool;
+import com.linbit.linstor.api.model.VolumeDefinition;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+@StorageAdaptorInfo(storagePoolType=Storage.StoragePoolType.Linstor)
+public class LinstorStorageAdaptor implements StorageAdaptor {
+    private static final Logger s_logger = Logger.getLogger(LinstorStorageAdaptor.class);
+    private static final Map<String, KVMStoragePool> MapStorageUuidToStoragePool = new HashMap<>();
+
+    private DevelopersApi getLinstorAPI(KVMStoragePool pool) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(pool.getSourceHost());
+        return new DevelopersApi(client);
+    }
+
+    private String getLinstorRscName(String name) {
+        return "cs-" + name;
+    }
+
+    private String getHostname() {
+        // either there is already some function for that in the agent or a better way.
+        ProcessBuilder pb = new ProcessBuilder("hostname");
+        try
+        {
+            String result;
+            Process p = pb.start();
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+            StringJoiner sj = new StringJoiner(System.getProperty("line.separator"));
+            reader.lines().iterator().forEachRemaining(sj::add);
+            result = sj.toString();
+
+            p.waitFor();
+            p.destroy();
+            return result;
+        } catch (IOException | InterruptedException exc) {
+            throw new CloudRuntimeException("Unable to run hostname command.");
+        }
+    }
+
+    private void handleLinstorApiAnswers(ApiCallRcList answers, String excMessage) {
+        if (ApiCallChecker.hasError(answers)) {
+            for (ApiCallRc answer : answers) {
+                s_logger.error(answer.getMessage());
+            }
+            throw new CloudRuntimeException(excMessage);
+        }
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid, boolean refreshInfo) {
+        s_logger.debug("Linstor getStoragePool: " + uuid + " -> " + refreshInfo);
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMPhysicalDisk getPhysicalDisk(String name, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: getPhysicalDisk for " + name);
+        if (name == null) {
+            return null;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try {
+            final String rscName = getLinstorRscName(name);
+
+            List<VolumeDefinition> volumeDefs = api.volumeDefinitionList(rscName, null, null);
+            final long size = volumeDefs.isEmpty() ? 0 : volumeDefs.get(0).getSizeKib() * 1024;
+
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty()) {
+                final String devPath = resources.get(0).getVolumes().get(0).getDevicePath();
+                final KVMPhysicalDisk kvmDisk = new KVMPhysicalDisk(devPath, name, pool);
+                kvmDisk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
+                kvmDisk.setSize(size);
+                kvmDisk.setVirtualSize(size);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes for " + rscName);
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiExc) {
+            s_logger.error(apiExc);
+            throw new CloudRuntimeException(apiExc);
+        }
+    }
+
+    @Override
+    public KVMStoragePool createStoragePool(String name, String host, int port, String path, String userInfo,
+                                            Storage.StoragePoolType type)
+    {
+        s_logger.debug(String.format(
+            "Linstor createStoragePool: name: '%s', host: '%s', path: %s, userinfo: %s", name, host, path, userInfo));
+        LinstorStoragePool storagePool = new LinstorStoragePool(name, host, port, userInfo, type, this);
+
+        MapStorageUuidToStoragePool.put(name, storagePool);
+
+        return storagePool;
+    }
+
+    @Override
+    public boolean deleteStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.remove(uuid) != null;
+    }
+
+    @Override
+    public boolean deleteStoragePool(KVMStoragePool pool) {
+        return deleteStoragePool(pool.getUuid());
+    }
+
+    @Override
+    public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, QemuImg.PhysicalDiskFormat format,
+                                              Storage.ProvisioningType provisioningType, long size)
+    {
+        LinstorStoragePool lpool = (LinstorStoragePool) pool;
+        final DevelopersApi api = getLinstorAPI(pool);
+        ResourceGroupSpawn rgSpawn = new ResourceGroupSpawn();
+        final String rscName = getLinstorRscName(name);
+        rgSpawn.setResourceDefinitionName(rscName);
+        rgSpawn.addVolumeSizesItem(size / 1024); // linstor uses KiB
+        try {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = api.resourceGroupSpawn(lpool.getResourceGroup(), rgSpawn);
+            handleLinstorApiAnswers(answers, "Linstor: Unable to spawn resource.");
+
+            // query linstor for the device path
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            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);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiExc) {
+            throw new CloudRuntimeException(apiExc);
+        }
+    }
+
+    @Override
+    public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details)
+    {
+        s_logger.debug(String.format("Linstor: connectPhysicalDisk %s:%s -> %s", pool.getUuid(), volumePath, details));
+        if (volumePath == null) {
+            s_logger.warn("volumePath is null, ignoring");
+            return false;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try
+        {
+            final String rscName = getLinstorRscName(volumePath);
+            final String localNode = getHostname();
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.singletonList(localNode),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+
+            if (resources.isEmpty()) {
+                s_logger.info("Linstor: resource not created yet: " + rscName);
+                ResourceCreate rscCreate = new ResourceCreate();
+                Resource rsc = new Resource();
+                rsc.setNodeName(localNode);
+                rscCreate.setResource(rsc);
+                ApiCallRcList answers = api.resourceCreate(rscName, Collections.singletonList(rscCreate));
+                if (ApiCallChecker.hasError(answers)) {
+                    s_logger.error("Could not create resource " + rscName);
+                    throw new CloudRuntimeException(answers.get(0).getMessage());
+                }
+                // now diskful resource should be deployed
+            }
+            // keep diskless, maybe add an option, but I'm concerned about out of space situations
+//            else {
+//                final ResourceWithVolumes rsc = resources.get(0);
+//                if (rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS)) {
+//                    ApiCallRcList answers = api.resourceToggleDiskful(rscName, localNode);
+//                    if (ApiCallChecker.hasError(answers)) {
+//                        s_logger.error("Unable to toggle disk on " + rscName);
+//                        throw new CloudRuntimeException(answers.get(0).getMessage());
+//                    }
+//                }
+//            }
+
+            // allow 2 primaries for live migration, should be removed by disconnect on the other end
+            ResourceDefinitionModify rdm = new ResourceDefinitionModify();
+            Properties props = new Properties();
+            props.put("DrbdOptions/Net/allow-two-primaries", "yes");
+            rdm.setOverrideProps(props);
+            ApiCallRcList answers = api.resourceDefinitionModify(rscName, rdm);
+            if (ApiCallChecker.hasError(answers)) {
+                s_logger.error("Unable to set 'allow-two-primaries' on " + rscName);
+                throw new CloudRuntimeException(answers.get(0).getMessage());
+            }
+        } catch (ApiException apiExc) {
+            s_logger.error(apiExc);
+            throw new CloudRuntimeException(apiExc.getMessage());
+        }
+        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(String volumePath, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDisk " + pool.getUuid() + ":" + volumePath);
+        // TODO remove diskless/diskful on node
+        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDisk map");
+        // TODO remove diskless/diskful on node
+        return true;
+    }
+
+    private Optional<ResourceWithVolumes> getResourceByPath(final List<ResourceWithVolumes> resources, String path) {
+        return resources.stream()
+            .filter(rsc -> rsc.getVolumes().stream()
+                .anyMatch(v -> v.getDevicePath().equals(path)))
+            .findFirst();
+    }
+
+    /**
+     * disconnectPhysicalDiskByPath is called after e.g. a live migration.
+     * The problem is we have no idea just from the path to which linstor-controller
+     * this resource would belong to. But as it should be highly unlikely that someone
+     * uses more than one linstor-controller to manage resource on the same kvm host.
+     * We will just take the first stored storagepool.
+     */
+    @Override
+    public boolean disconnectPhysicalDiskByPath(String localPath)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDiskByPath " + localPath);
+        // get first storage pool from the map, as we don't now any better:
+        if (!MapStorageUuidToStoragePool.isEmpty())
+        {
+            String firstKey = MapStorageUuidToStoragePool.keySet().stream().findFirst().get();
+            final KVMStoragePool pool = MapStorageUuidToStoragePool.get(firstKey);
+
+            s_logger.debug("Linstor: Using storpool: " + pool.getUuid());
+            final DevelopersApi api = getLinstorAPI(pool);
+
+            try
+            {
+                List<ResourceWithVolumes> resources = api.viewResources(
+                    Collections.singletonList(getHostname()),
+                    null,
+                    null,
+                    null,
+                    null,
+                    null);
+
+                Optional<ResourceWithVolumes> rsc = getResourceByPath(resources, localPath);
+
+                if (rsc.isPresent())
+                {
+                    ResourceDefinitionModify rdm = new ResourceDefinitionModify();
+                    rdm.deleteProps(Collections.singletonList("DrbdOptions/Net/allow-two-primaries"));
+                    ApiCallRcList answers = api.resourceDefinitionModify(rsc.get().getName(), rdm);
+                    if (ApiCallChecker.hasError(answers))
+                    {
+                        s_logger.error("Failed to remove 'allow-two-primaries' on " + rsc.get().getName());
+                        throw new CloudRuntimeException(answers.get(0).getMessage());
+                    }
+
+                    return true;
+                }
+                s_logger.warn("Linstor: Couldn't find resource for this path: " + localPath);
+            } catch (ApiException apiExc) {
+                s_logger.error(apiExc);
+                throw new CloudRuntimeException(apiExc.getMessage());
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean deletePhysicalDisk(String name, KVMStoragePool pool, Storage.ImageFormat format)
+    {
+        s_logger.debug("Linstor: deletePhysicalDisk " + name);
+        final DevelopersApi api = getLinstorAPI(pool);
+
+        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);
+        } catch (ApiException apiExc) {
+            throw new CloudRuntimeException(apiExc);
+        }
+        return true;
+    }
+
+    @Override
+    public KVMPhysicalDisk createDiskFromTemplate(
+        KVMPhysicalDisk template,
+        String name,
+        QemuImg.PhysicalDiskFormat format,
+        Storage.ProvisioningType provisioningType,
+        long size,
+        KVMStoragePool destPool,
+        int timeout)
+    {
+        s_logger.info("Linstor: createDiskFromTemplate");
+        // if source is linstor lvm-thin snapshot we could snapshot and restore from, but do plain copy for now

Review comment:
       @rp- I do not know Linstor and do not know why you choose to use this way, but I am wondering why do a plain copy instead of a snapshot...




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-832723911


   > @rp- a can you at least raise a documentation PR (https://github.com/apache/cloudstack-documentation) to explain how to use this and some details on how this was tested. It would be great if you can share a design document of this feature (for example, https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=158873968 a new storage plugin).
   
   @rhtyd 
   I created an account at cwiki.apache.org, but I'm not sure how, or if I can create such a design document there yet?
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-832423758


   <b>Trillian test result (tid-604)</b>
   Environment: kvm-centos7 (x2), Advanced Networking with Mgmt server 7
   Total time taken: 50396 seconds
   Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr4994-t604-kvm-centos7.zip
   Intermittent failure detected: /marvin/tests/smoke/test_reset_vm_on_reboot.py
   Intermittent failure detected: /marvin/tests/smoke/test_resource_accounting.py
   Intermittent failure detected: /marvin/tests/smoke/test_router_dhcphosts.py
   Intermittent failure detected: /marvin/tests/smoke/test_routers_network_ops.py
   Intermittent failure detected: /marvin/tests/smoke/test_vpc_redundant.py
   Intermittent failure detected: /marvin/tests/smoke/test_hostha_kvm.py
   Smoke tests completed. 82 look OK, 6 have error(s)
   Only failed tests results shown below:
   
   
   Test | Result | Time (s) | Test File
   --- | --- | --- | ---
   ContextSuite context=TestResetVmOnReboot>:setup | `Error` | 0.00 | test_reset_vm_on_reboot.py
   ContextSuite context=TestRAMCPUResourceAccounting>:setup | `Error` | 0.00 | test_resource_accounting.py
   test_router_dhcphosts | `Failure` | 216.62 | test_router_dhcphosts.py
   ContextSuite context=TestRouterDHCPHosts>:teardown | `Error` | 226.91 | test_router_dhcphosts.py
   test_01_RVR_Network_FW_PF_SSH_default_routes_egress_true | `Failure` | 306.12 | test_routers_network_ops.py
   test_03_create_redundant_VPC_1tier_2VMs_2IPs_2PF_ACL_reboot_routers | `Failure` | 423.77 | test_vpc_redundant.py
   test_disable_oobm_ha_state_ineligible | `Error` | 1511.56 | test_hostha_kvm.py
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-832754859


   @rp- can you join our mailing list and drop a line to dev@ http://cloudstack.apache.org/mailing-lists.html I think someone will access will grant you permission to add/edit pages (cc @DaanHoogland - do you have management access or do we ask ASF infra?)
   
   @blueorangutan package


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-832755467


   @rhtyd a Jenkins job has been kicked to build packages. I'll keep you posted as I make progress.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-870344074


   Ping @rp- any update for us - is this ready for review/testing, and functionally complete from your side?
   @blueorangutan package


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] sureshanaparti commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
sureshanaparti commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r626282089



##########
File path: plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java
##########
@@ -0,0 +1,195 @@
+package com.cloud.hypervisor.kvm.storage;
+
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.storage.Storage;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+
+public class LinstorStoragePool implements KVMStoragePool {
+    private final String _uuid;
+    private final String _sourceHost;
+    private final int _sourcePort;
+    private final Storage.StoragePoolType _storagePoolType;
+    private final StorageAdaptor _storageAdaptor;
+    private final String _resourceGroup;
+
+    public LinstorStoragePool(
+            String uuid,
+            String host,
+            int port,
+            String resourceGroup,
+            Storage.StoragePoolType storagePoolType,
+            StorageAdaptor storageAdaptor) {
+        _uuid = uuid;
+        _sourceHost = host;
+        _sourcePort = port;
+        _storagePoolType = storagePoolType;
+        _storageAdaptor = storageAdaptor;
+        _resourceGroup = resourceGroup;
+    }
+
+    @Override
+    public KVMPhysicalDisk createPhysicalDisk(
+        String name,
+        QemuImg.PhysicalDiskFormat format,
+        Storage.ProvisioningType provisioningType,
+        long size)
+    {
+        return this._storageAdaptor.createPhysicalDisk(
+            name,
+            this,
+            format,
+            provisioningType,
+            size
+        );
+    }
+
+    @Override
+    public KVMPhysicalDisk createPhysicalDisk(String volumeUuid, Storage.ProvisioningType provisioningType, long size)
+    {
+        return this._storageAdaptor.createPhysicalDisk(
+            volumeUuid,
+            this,
+            getDefaultFormat(),
+            provisioningType,
+            size
+        );
+    }
+
+    @Override
+    public boolean connectPhysicalDisk(String volumeUuid, Map<String, String> details)
+    {
+        return this._storageAdaptor.connectPhysicalDisk(volumeUuid, this, details);
+    }
+
+    @Override
+    public KVMPhysicalDisk getPhysicalDisk(String volumeUuid)
+    {
+        return this._storageAdaptor.getPhysicalDisk(volumeUuid, this);

Review comment:
       I think, no need to explicitly mention _this_  for class method / variable unless one exists with same name in that scope.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd edited a comment on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd edited a comment on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-899457160


   @rp- yes, other plugins for example in case of ScaleIO and Ceph, the template is converted to RAW; the specific disk/file/drive is mounted on KVM host on which qemu-img is used to convert a guest template from a valid source (qcow2, raw...) to raw. It's completely fine to implement a sub-set of storage feature for Linstor and specific/enforce a natively supported storage/format (such as raw).
   
   /cc @sureshanaparti  (looping in Suresh who wrote the ScaleIO storage plugin)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-832016144


   @blueorangutan test 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-870344947


   @rhtyd a Jenkins job has been kicked to build packages. I'll keep you posted as I make progress.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] sureshanaparti commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
sureshanaparti commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r626285546



##########
File path: plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
##########
@@ -0,0 +1,420 @@
+// 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.driver;
+
+import com.linbit.linstor.api.ApiCallChecker;
+import com.linbit.linstor.api.ApiException;
+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.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
+import com.linbit.linstor.api.model.VolumeDefinitionModify;
+
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.to.DataObjectType;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.host.Host;
+import com.cloud.storage.ResizeVolumePayload;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
+import org.apache.cloudstack.storage.volume.VolumeObject;
+import org.apache.log4j.Logger;
+
+public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver {
+    private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class);
+    @Inject
+    private PrimaryDataStoreDao _storagePoolDao;
+    @Inject
+    private VolumeDao _volumeDao;
+
+    public LinstorPrimaryDataStoreDriverImpl()
+    {
+    }
+
+    @Override
+    public Map<String, String> getCapabilities()
+    {
+        Map<String, String> mapCapabilities = new HashMap<>();
+
+        // Linstor can create a volume from a volume via snapshot restore, but this would only work
+        // if a LVM-thin storage pool is used, TBH it would also work with ZFS pools, but there you always
+        // have to keep in mind the child-parent snapshot relationship
+        // So we will rely on a plain copy volume mechanism which is more robust for now
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString(), Boolean.FALSE.toString());
+
+        // for now, technically possible
+        mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.FALSE.toString());
+        // CAN_CREATE_VOLUME_FROM_SNAPSHOT see note from CAN_CREATE_VOLUME_FROM_VOLUME
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.FALSE.toString());
+        mapCapabilities.put(DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString(), Boolean.FALSE.toString());
+
+        mapCapabilities.put(DataStoreCapabilities.VOLUME_SNAPSHOT_QUIESCEVM.toString(), Boolean.FALSE.toString());
+
+        return mapCapabilities;
+    }
+
+    @Override
+    public DataTO getTO(DataObject data)
+    {
+        return null;
+    }
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store)
+    {
+        return null;
+    }
+
+    @Override
+    public ChapInfo getChapInfo(DataObject dataObject)
+    {
+        return null;
+    }
+
+    @Override
+    public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+        return false;
+    }
+
+    @Override
+    public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+    }
+
+    @Override
+    public long getUsedBytes(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getUsedIops(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool)
+    {
+        return dataObject.getSize();
+    }
+
+    @Override
+    public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    private void deleteResourceDefinition(DataStore dataStore, String rscDefName)
+    {
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceDefinitionDelete(rscDefName);
+            if (ApiCallChecker.hasError(answers))
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete resource definition: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx);
+        }
+    }
+
+    @Override
+    public void deleteAsync(
+        DataStore dataStore,
+        DataObject dataObject,
+        AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("deleteAsync: " + dataObject.getType() + ";" + dataObject.getId());
+        String errMsg = null;
+
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+
+        if (dataObject.getType() == DataObjectType.VOLUME)
+        {
+            VolumeInfo volumeInfo = (VolumeInfo) dataObject;
+
+            long storagePoolId = dataStore.getId();
+            final String rscName = "cs-" + volumeInfo.getPath();

Review comment:
       Better to define the prefix "_cs-_" as string constant in some common class, and use it across.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] GutoVeronezi commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
GutoVeronezi commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-836841503


   @rp- I noticed that you added a lot of comments to the code. Most of they only describe what the next line is doing, e.q.: 
   ```java
   //verify if var is true then print it in console
   if (var) {
       System.out.println("var is true");
   }
   ```
   
   These types of comments do not improve debugging and goes against a clean code. However, there are comments that explain a bit of the process, but they could be resolved with an extraction of the code to a method with a corresponding name, javadoc and etc.
   
   Also, there are `TODO` comments. These requests should be regularized in issues, not in code.
   
   Comments should be a last resort, not the rule. If we take all the comments and sort then in a logic way, we could write a book :smile: .


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-919693252


   @nvazquez a Trillian-Jenkins test job (centos7 mgmt + kvm-centos7) has been kicked to run smoke tests


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-918248529


   @nvazquez a Trillian-Jenkins test job (centos7 mgmt + kvm-centos7) has been kicked to run smoke tests


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd closed pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd closed pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994


   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd closed pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd closed pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994


   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-918908430


   > @rp- can you fix the conflicts? I think this is otherwise good to merge.
   
   done


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] nvazquez commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
nvazquez commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-918246670


   @blueorangutan test


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909202520


   @blueorangutan test 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909251571


   @rhtyd a Trillian-Jenkins test job (centos7 mgmt + kvm-centos7) has been kicked to run smoke tests


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-889038592


   @andrijapanicsb @rhtyd 
   As stated above, I noticed while testing on a new cluster with ZFS, that current implemention doesn't work out as planned.
   So I'm currently implementing a new feature in Linstor(Clone) to fix this and I'm nearly done with it. Have to do some more testing and writing tests on Linstor side.
   After this is done, I'll update the plugin and do a force push of the plugin driver. It probably doesn't make sense to review it in the current state.
   I'll try to get the code updated next week.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-878888248


   Asking our RMs - @nvazquez @sureshanaparti to help kick tests and review.
   
   @rp- We don't have a Linstor setup to test your changes so we can help kick regression smoketests and maybe you can provide some test results.
   
   For example, you can run and share results of your smoketests in your env (or using virtualised https://github.com/shapeblue/mbx) with Linstor storage with KVM as the only primary storage. Or a set of manual tests. We can merge based on your provided test results and regression smoketests, and after all review items are addressed.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] DaanHoogland commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
DaanHoogland commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-889936032


   @rp- I'm putting this back into draft because of your last remark.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] slavkap commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
slavkap commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-870390972






-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-878884230


   @shwstppr a Jenkins job has been kicked to build packages. I'll keep you posted as I make progress.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-838073695


   I added a basic PR for documentation and also a first version of the design document:
   https://cwiki.apache.org/confluence/display/CLOUDSTACK/Linstor+storage+plugin
   (sorry I mostly copied the stuff from the ScaleIO plugin)
   
   Btw. do you have some storage test guidelines? Like what basic functions should work and how to test them?
   I'm not to familiar with all Cloudstack functionalities, so chances are high that I simply missed some functionality tests.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-831972670


   @rp- a can you at least raise a documentation PR (https://github.com/apache/cloudstack-documentation) to explain how to use this and some details on how this was tested. It would be great if you can share a design document of this feature (for example, https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=158873968 a new storage plugin).
   
   I can help kick regression tests in the meanwhile.
   @blueorangutan package


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-870344947






-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] weizhouapache commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
weizhouapache commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r627356886



##########
File path: ui/public/locales/en.json
##########
@@ -3060,6 +3061,7 @@
 "message.reset.password.warning.notpasswordenabled": "The template of this instance was created without password enabled",
 "message.reset.password.warning.notstopped": "Your instance must be stopped before attempting to change its current password",
 "message.reset.vpn.connection": "Please confirm that you want to reset VPN connection",
+"message.resourcegroup.description": "Linstor resource group to use for primary storage",

Review comment:
       can you add "linstor" into the key "message.resourcegroup.description" ?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-880456756


   @rhtyd a Trillian-Jenkins test job (centos7 mgmt + kvm-centos7) has been kicked to run smoke tests


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r629880539



##########
File path: plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
##########
@@ -0,0 +1,566 @@
+package com.cloud.hypervisor.kvm.storage;
+
+import com.linbit.linstor.api.ApiCallChecker;
+import com.linbit.linstor.api.ApiClient;
+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;
+import com.linbit.linstor.api.model.Properties;
+import com.linbit.linstor.api.model.ProviderKind;
+import com.linbit.linstor.api.model.Resource;
+import com.linbit.linstor.api.model.ResourceCreate;
+import com.linbit.linstor.api.model.ResourceDefinitionModify;
+import com.linbit.linstor.api.model.ResourceGroup;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
+import com.linbit.linstor.api.model.StoragePool;
+import com.linbit.linstor.api.model.VolumeDefinition;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+@StorageAdaptorInfo(storagePoolType=Storage.StoragePoolType.Linstor)
+public class LinstorStorageAdaptor implements StorageAdaptor {
+    private static final Logger s_logger = Logger.getLogger(LinstorStorageAdaptor.class);
+    private static final Map<String, KVMStoragePool> MapStorageUuidToStoragePool = new HashMap<>();
+
+    private DevelopersApi getLinstorAPI(KVMStoragePool pool) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(pool.getSourceHost());
+        return new DevelopersApi(client);
+    }
+
+    private String getLinstorRscName(String name) {
+        return "cs-" + name;
+    }
+
+    private String getHostname() {
+        // either there is already some function for that in the agent or a better way.
+        ProcessBuilder pb = new ProcessBuilder("hostname");
+        try
+        {
+            String result;
+            Process p = pb.start();
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+            StringJoiner sj = new StringJoiner(System.getProperty("line.separator"));
+            reader.lines().iterator().forEachRemaining(sj::add);
+            result = sj.toString();

Review comment:
       I may not even need this whole function if there is a way how I could get the running agent node/host name.
   Do you know if there is something like that within the agent code?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] andrijapanicsb commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
andrijapanicsb commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r702851940



##########
File path: plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
##########
@@ -0,0 +1,720 @@
+// 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.driver;
+
+import com.linbit.linstor.api.ApiException;
+import com.linbit.linstor.api.CloneWaiter;
+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.ResourceDefinition;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted;
+import com.linbit.linstor.api.model.ResourceDefinitionCreate;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+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.VolumeDefinitionModify;
+
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.agent.api.to.DiskTO;
+import com.cloud.host.Host;
+import com.cloud.storage.ResizeVolumePayload;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.VolumeDetailVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.SnapshotDetailsDao;
+import com.cloud.storage.dao.SnapshotDetailsVO;
+import com.cloud.storage.dao.VMTemplatePoolDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.storage.command.CreateObjectAnswer;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
+import org.apache.cloudstack.storage.volume.VolumeObject;
+import org.apache.log4j.Logger;
+
+public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver {
+    private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class);
+    @Inject private PrimaryDataStoreDao _storagePoolDao;
+    @Inject private VolumeDao _volumeDao;
+    @Inject private VolumeDetailsDao _volumeDetailsDao;
+    @Inject private VMTemplatePoolDao _vmTemplatePoolDao;
+    @Inject private SnapshotDao _snapshotDao;
+    @Inject private SnapshotDetailsDao _snapshotDetailsDao;
+
+    public LinstorPrimaryDataStoreDriverImpl()
+    {
+    }
+
+    @Override
+    public Map<String, String> getCapabilities()
+    {
+        Map<String, String> mapCapabilities = new HashMap<>();
+
+        // Linstor will be restricted to only run on LVM-THIN and ZFS storage pools with ACS
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString(), Boolean.TRUE.toString());
+
+        // fetch if lvm-thin or ZFS
+        mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        // CAN_CREATE_VOLUME_FROM_SNAPSHOT see note from CAN_CREATE_VOLUME_FROM_VOLUME
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+        mapCapabilities.put(DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        return mapCapabilities;
+    }
+
+    @Override
+    public DataTO getTO(DataObject data)
+    {
+        return null;
+    }
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store)
+    {
+        return null;
+    }
+
+    @Override
+    public ChapInfo getChapInfo(DataObject dataObject)
+    {
+        return null;
+    }
+
+    @Override
+    public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+        return false;
+    }
+
+    @Override
+    public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+    }
+
+    @Override
+    public long getUsedBytes(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getUsedIops(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool)
+    {
+        return dataObject.getSize();
+    }
+
+    @Override
+    public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    private String getSnapshotName(String snapshotUuid) {
+        return LinstorUtil.RSC_PREFIX + snapshotUuid;
+    }
+
+    private void deleteResourceDefinition(StoragePoolVO storagePoolVO, String rscDefName)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceDefinitionDelete(rscDefName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete resource definition: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private void deleteSnapshot(@Nonnull DataStore dataStore, @Nonnull String rscDefName, @Nonnull String snapshotName)
+    {
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceSnapshotDelete(rscDefName, snapshotName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete snapshot: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private long getCsIdForCloning(long volumeId, String cloneOf) {
+        VolumeDetailVO volumeDetail = _volumeDetailsDao.findDetail(volumeId, cloneOf);
+
+        if (volumeDetail != null && volumeDetail.getValue() != null) {
+            return Long.parseLong(volumeDetail.getValue());
+        }
+
+        return Long.MIN_VALUE;
+    }
+
+    @Override
+    public void deleteAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("deleteAsync: " + dataObject.getType() + ";" + dataObject.getUuid());
+        String errMsg = null;
+
+        final long storagePoolId = dataStore.getId();
+        final StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
+
+        switch (dataObject.getType()) {
+            case VOLUME:
+            {
+                final VolumeInfo volumeInfo = (VolumeInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getPath();
+                deleteResourceDefinition(storagePool, rscName);
+
+                long usedBytes = storagePool.getUsedBytes();
+                long capacityIops = storagePool.getCapacityIops();
+
+                usedBytes -= volumeInfo.getSize();
+                if (volumeInfo.getMaxIops() != null)
+                    capacityIops += volumeInfo.getMaxIops();
+
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                storagePool.setCapacityIops(Math.max(0, capacityIops));
+
+                _storagePoolDao.update(storagePoolId, storagePool);
+            }
+                break;
+            case SNAPSHOT:
+                final SnapshotInfo snapshotInfo = (SnapshotInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + snapshotInfo.getBaseVolume().getPath();
+                deleteSnapshot(dataStore, rscName, getSnapshotName(snapshotInfo.getUuid()));
+                long usedBytes = storagePool.getUsedBytes() - snapshotInfo.getSize();
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                _storagePoolDao.update(storagePoolId, storagePool);
+                break;
+            default:
+                errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync";
+                s_logger.error(errMsg);
+        }
+
+        if (callback != null) {
+            CommandResult result = new CommandResult();
+            result.setResult(errMsg);
+
+            callback.complete(result);
+        }
+    }
+
+    private void logLinstorAnswer(@Nonnull ApiCallRc answer) {
+        if (answer.isError()) {
+            s_logger.error(answer.getMessage());
+        } else if (answer.isWarning()) {
+            s_logger.warn(answer.getMessage());
+        } else if (answer.isInfo()) {
+            s_logger.info(answer.getMessage());
+        }
+    }
+
+    private void logLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        answers.forEach(this::logLinstorAnswer);
+    }
+
+    private void checkLinstorAnswersThrow(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        if (answers.hasError())
+        {
+            String errMsg = answers.stream()
+                .filter(ApiCallRc::isError)
+                .findFirst()
+                .map(ApiCallRc::getMessage).orElse("Unknown linstor error");
+            throw new CloudRuntimeException(errMsg);
+        }
+    }
+
+    private String checkLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        return answers.stream().filter(ApiCallRc::isError).findFirst().map(ApiCallRc::getMessage).orElse(null);
+    }
+
+    private String getDeviceName(DevelopersApi linstorApi, String rscName) throws ApiException {
+        List<ResourceWithVolumes> resources = linstorApi.viewResources(
+            Collections.emptyList(),
+            Collections.singletonList(rscName),
+            Collections.emptyList(),
+            null,
+            null,
+            null);
+        if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty())
+        {
+            s_logger.info("Linstor: Created drbd device: " + resources.get(0).getVolumes().get(0).getDevicePath());
+            return resources.get(0).getVolumes().get(0).getDevicePath();
+        } else
+        {
+            s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+            throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+        }
+    }
+
+    private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+
+        ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn();
+        final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
+        rscGrpSpawn.setResourceDefinitionName(rscName);
+        rscGrpSpawn.addVolumeSizesItem(vol.getSize() / 1024);
+
+        try
+        {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = linstorApi.resourceGroupSpawn(rscGrp, rscGrpSpawn);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String cloneResource(long csCloneId, VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        // get the cached template on this storage
+        VMTemplateStoragePoolVO tmplPoolRef = _vmTemplatePoolDao.findByPoolTemplate(
+            storagePoolVO.getId(), csCloneId, null);
+
+        if (tmplPoolRef != null) {
+            final String cloneRes = LinstorUtil.RSC_PREFIX + tmplPoolRef.getLocalDownloadPath();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+            try {
+                s_logger.debug("Clone resource definition " + cloneRes + " to " + rscName);
+                ResourceDefinitionCloneRequest cloneRequest = new ResourceDefinitionCloneRequest();
+                cloneRequest.setName(rscName);
+                ResourceDefinitionCloneStarted cloneStarted = linstorApi.resourceDefinitionClone(
+                    cloneRes, cloneRequest);
+
+                checkLinstorAnswersThrow(cloneStarted.getMessages());
+
+                if (!CloneWaiter.waitFor(linstorApi, cloneStarted)) {
+                    throw new CloudRuntimeException("Clone for resource " + rscName + " failed.");
+                }
+
+                return getDeviceName(linstorApi, rscName);
+            } catch (ApiException apiEx) {
+                s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+                throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+            }
+        } else {
+            throw new CloudRuntimeException(
+                "Unable to find Linstor resource for the following template data-object ID: " + csCloneId);
+        }
+    }
+
+    private String createResourceFromSnapshot(long csSnapshotId, String rscName, StoragePoolVO storagePoolVO) {
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+        final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        SnapshotVO snapshotVO = _snapshotDao.findById(csSnapshotId);
+        String snapName = LinstorUtil.RSC_PREFIX + snapshotVO.getUuid();
+        VolumeVO volumeVO = _volumeDao.findById(snapshotVO.getVolumeId());
+        String cloneRes = LinstorUtil.RSC_PREFIX + volumeVO.getPath();
+
+        try
+        {
+            s_logger.debug("Create new resource definition: " + rscName);
+            ResourceDefinitionCreate rdCreate = new ResourceDefinitionCreate();
+            ResourceDefinition rd = new ResourceDefinition();
+            rd.setName(rscName);
+            rd.setResourceGroupName(rscGrp);
+            rdCreate.setResourceDefinition(rd);
+            ApiCallRcList answers = linstorApi.resourceDefinitionCreate(rdCreate);
+            checkLinstorAnswersThrow(answers);
+
+            SnapshotRestore snapshotRestore = new SnapshotRestore();
+            snapshotRestore.toResource(rscName);
+
+            s_logger.debug("Create new volume definition for snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotsRestoreVolumeDefinition(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            // restore snapshot to new resource
+            s_logger.debug("Restore resource from snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotRestore(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String createVolume(VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = getCsIdForCloning(volumeInfo.getId(), "cloneOfSnapshot");
+        long csTemplateId = getCsIdForCloning(volumeInfo.getId(), "cloneOfTemplate");
+
+        if (csSnapshotId > 0) {
+            return createResourceFromSnapshot(csSnapshotId, LinstorUtil.RSC_PREFIX + volumeInfo.getUuid(), storagePoolVO);
+        } else if (csTemplateId > 0) {
+            return cloneResource(csTemplateId, volumeInfo, storagePoolVO);
+        } else {
+            return createResource(volumeInfo, storagePoolVO);
+        }
+    }
+
+    private void handleSnapshotDetails(long csSnapshotId, String name, String value) {
+        _snapshotDetailsDao.removeDetail(csSnapshotId, name);
+        SnapshotDetailsVO snapshotDetails = new SnapshotDetailsVO(csSnapshotId, name, value, false);
+        _snapshotDetailsDao.persist(snapshotDetails);
+    }
+
+    private void addTempVolumeToDb(long csSnapshotId, String tempVolumeName) {
+        // TEMP_VOLUME_ID is needed, to find which temporary resource should be deleted after copying it on agent side
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, LinstorUtil.RSC_PREFIX + tempVolumeName);
+        // the iqn will be used on the agent side to copy from, even though linstor doesn't have anything to do with IQN
+        handleSnapshotDetails(csSnapshotId, DiskTO.IQN, tempVolumeName);
+    }
+
+    private void removeTempVolumeFromDb(long csSnapshotId) {
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+        if (snapshotDetails == null || snapshotDetails.getValue() == null) {
+            throw new CloudRuntimeException(
+                "'removeTempVolumeId' should not be invoked unless " + LinstorUtil.TEMP_VOLUME_ID + " exists.");
+        }
+
+        String originalVolumeId = snapshotDetails.getValue();
+
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, originalVolumeId);
+
+        _snapshotDetailsDao.remove(snapshotDetails.getId());
+    }
+
+    private void createVolumeFromSnapshot(SnapshotInfo snapshotInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = snapshotInfo.getId();
+
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, "tempVolume");
+
+        if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("create"))
+        {
+            final String csName = "Temp-" + snapshotInfo.getUuid();
+            final String tempRscName = LinstorUtil.RSC_PREFIX + csName;
+            createResourceFromSnapshot(csSnapshotId, tempRscName, storagePoolVO);
+
+            s_logger.debug("Temp resource created: " + tempRscName);
+            addTempVolumeToDb(csSnapshotId, csName);
+        }
+        else if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("delete"))
+        {
+            snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+            deleteResourceDefinition(storagePoolVO, snapshotDetails.getValue());
+
+            s_logger.debug("Temp resource deleted: " + snapshotDetails.getValue());
+            removeTempVolumeFromDb(csSnapshotId);
+        }
+        else {
+            throw new CloudRuntimeException("Invalid state in 'createVolumeFromSnapshot(SnapshotInfo, StoragePoolVO)'");
+        }
+    }
+
+    @Override
+    public void createAsync(DataStore dataStore, DataObject vol, AsyncCompletionCallback<CreateCmdResult> callback)
+    {
+        String devPath = null;
+        String errMsg = null;
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+
+        try
+        {
+            switch (vol.getType())
+            {
+                case VOLUME:
+                    VolumeInfo volumeInfo = (VolumeInfo) vol;
+                    VolumeVO volume = _volumeDao.findById(volumeInfo.getId());
+                    s_logger.debug("createAsync - creating volume");
+                    devPath = createVolume(volumeInfo, storagePool);
+                    volume.setFolder("/dev/");
+                    volume.setPoolId(storagePool.getId());
+                    volume.setUuid(vol.getUuid());
+                    volume.setPath(vol.getUuid());
+
+                    _volumeDao.update(volume.getId(), volume);
+                    break;
+                case SNAPSHOT:
+                    s_logger.debug("createAsync - SNAPSHOT");
+                    createVolumeFromSnapshot((SnapshotInfo) vol, storagePool);
+                    break;
+                case TEMPLATE:
+                    errMsg = "creating template - not supported";
+                    s_logger.error("createAsync - " + errMsg);
+                    break;
+                default:
+                    errMsg = "Invalid DataObjectType (" + vol.getType() + ") passed to createAsync";
+                    s_logger.error(errMsg);
+            }
+        } catch (Exception ex)
+        {
+            errMsg = ex.getMessage();
+
+            s_logger.error("createAsync: " + errMsg);
+            if (callback == null)
+            {
+                throw ex;
+            }
+        }
+
+        if (callback != null)
+        {
+            CreateCmdResult result = new CreateCmdResult(devPath, new Answer(null, errMsg == null, errMsg));
+            result.setResult(errMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public void revertSnapshot(
+        SnapshotInfo snapshot,
+        SnapshotInfo snapshotOnPrimaryStore,
+        AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("Linstor: revertSnapshot");
+        final VolumeInfo volumeInfo = snapshot.getBaseVolume();
+        VolumeVO volumeVO = _volumeDao.findById(volumeInfo.getId());
+        if (volumeVO == null || volumeVO.getRemoved() != null) {
+            CommandResult commandResult = new CommandResult();
+            commandResult.setResult("The volume that the snapshot belongs to no longer exists.");
+            callback.complete(commandResult);
+            return;
+        }
+
+        String resultMsg;
+        try {
+            final StoragePool pool = (StoragePool) snapshot.getDataStore();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final String snapName = LinstorUtil.RSC_PREFIX + snapshot.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(pool.getHostAddress());
+
+            ApiCallRcList answers = linstorApi.resourceSnapshotRollback(rscName, snapName);
+            resultMsg = checkLinstorAnswers(answers);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            resultMsg = apiEx.getBestMessage();
+        }
+
+        if (callback != null)
+        {
+            CommandResult result = new CommandResult();
+            result.setResult(resultMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public boolean canCopy(DataObject srcData, DataObject destData)
+    {
+        return false;
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback)

Review comment:
       Do we do this for other storage types? I'm not aware we support online disk resize for other storage platforms, but I may be wrong.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909059008


   Ping @rp- any update? The 4.16 freeze may be around the corner soon (say 2 weeks)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r704191606



##########
File path: plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
##########
@@ -0,0 +1,720 @@
+// 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.driver;
+
+import com.linbit.linstor.api.ApiException;
+import com.linbit.linstor.api.CloneWaiter;
+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.ResourceDefinition;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted;
+import com.linbit.linstor.api.model.ResourceDefinitionCreate;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+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.VolumeDefinitionModify;
+
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.agent.api.to.DiskTO;
+import com.cloud.host.Host;
+import com.cloud.storage.ResizeVolumePayload;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.VolumeDetailVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.SnapshotDetailsDao;
+import com.cloud.storage.dao.SnapshotDetailsVO;
+import com.cloud.storage.dao.VMTemplatePoolDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.storage.command.CreateObjectAnswer;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
+import org.apache.cloudstack.storage.volume.VolumeObject;
+import org.apache.log4j.Logger;
+
+public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver {
+    private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class);
+    @Inject private PrimaryDataStoreDao _storagePoolDao;
+    @Inject private VolumeDao _volumeDao;
+    @Inject private VolumeDetailsDao _volumeDetailsDao;
+    @Inject private VMTemplatePoolDao _vmTemplatePoolDao;
+    @Inject private SnapshotDao _snapshotDao;
+    @Inject private SnapshotDetailsDao _snapshotDetailsDao;
+
+    public LinstorPrimaryDataStoreDriverImpl()
+    {
+    }
+
+    @Override
+    public Map<String, String> getCapabilities()
+    {
+        Map<String, String> mapCapabilities = new HashMap<>();
+
+        // Linstor will be restricted to only run on LVM-THIN and ZFS storage pools with ACS
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString(), Boolean.TRUE.toString());
+
+        // fetch if lvm-thin or ZFS
+        mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        // CAN_CREATE_VOLUME_FROM_SNAPSHOT see note from CAN_CREATE_VOLUME_FROM_VOLUME
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+        mapCapabilities.put(DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        return mapCapabilities;
+    }
+
+    @Override
+    public DataTO getTO(DataObject data)
+    {
+        return null;
+    }
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store)
+    {
+        return null;
+    }
+
+    @Override
+    public ChapInfo getChapInfo(DataObject dataObject)
+    {
+        return null;
+    }
+
+    @Override
+    public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+        return false;
+    }
+
+    @Override
+    public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+    }
+
+    @Override
+    public long getUsedBytes(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getUsedIops(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool)
+    {
+        return dataObject.getSize();
+    }
+
+    @Override
+    public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    private String getSnapshotName(String snapshotUuid) {
+        return LinstorUtil.RSC_PREFIX + snapshotUuid;
+    }
+
+    private void deleteResourceDefinition(StoragePoolVO storagePoolVO, String rscDefName)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceDefinitionDelete(rscDefName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete resource definition: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private void deleteSnapshot(@Nonnull DataStore dataStore, @Nonnull String rscDefName, @Nonnull String snapshotName)
+    {
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceSnapshotDelete(rscDefName, snapshotName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete snapshot: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private long getCsIdForCloning(long volumeId, String cloneOf) {
+        VolumeDetailVO volumeDetail = _volumeDetailsDao.findDetail(volumeId, cloneOf);
+
+        if (volumeDetail != null && volumeDetail.getValue() != null) {
+            return Long.parseLong(volumeDetail.getValue());
+        }
+
+        return Long.MIN_VALUE;
+    }
+
+    @Override
+    public void deleteAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("deleteAsync: " + dataObject.getType() + ";" + dataObject.getUuid());
+        String errMsg = null;
+
+        final long storagePoolId = dataStore.getId();
+        final StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
+
+        switch (dataObject.getType()) {
+            case VOLUME:
+            {
+                final VolumeInfo volumeInfo = (VolumeInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getPath();
+                deleteResourceDefinition(storagePool, rscName);
+
+                long usedBytes = storagePool.getUsedBytes();
+                long capacityIops = storagePool.getCapacityIops();
+
+                usedBytes -= volumeInfo.getSize();
+                if (volumeInfo.getMaxIops() != null)
+                    capacityIops += volumeInfo.getMaxIops();
+
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                storagePool.setCapacityIops(Math.max(0, capacityIops));
+
+                _storagePoolDao.update(storagePoolId, storagePool);
+            }
+                break;
+            case SNAPSHOT:
+                final SnapshotInfo snapshotInfo = (SnapshotInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + snapshotInfo.getBaseVolume().getPath();
+                deleteSnapshot(dataStore, rscName, getSnapshotName(snapshotInfo.getUuid()));
+                long usedBytes = storagePool.getUsedBytes() - snapshotInfo.getSize();
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                _storagePoolDao.update(storagePoolId, storagePool);
+                break;
+            default:
+                errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync";
+                s_logger.error(errMsg);
+        }
+
+        if (callback != null) {
+            CommandResult result = new CommandResult();
+            result.setResult(errMsg);
+
+            callback.complete(result);
+        }
+    }
+
+    private void logLinstorAnswer(@Nonnull ApiCallRc answer) {
+        if (answer.isError()) {
+            s_logger.error(answer.getMessage());
+        } else if (answer.isWarning()) {
+            s_logger.warn(answer.getMessage());
+        } else if (answer.isInfo()) {
+            s_logger.info(answer.getMessage());
+        }
+    }
+
+    private void logLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        answers.forEach(this::logLinstorAnswer);
+    }
+
+    private void checkLinstorAnswersThrow(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        if (answers.hasError())
+        {
+            String errMsg = answers.stream()
+                .filter(ApiCallRc::isError)
+                .findFirst()
+                .map(ApiCallRc::getMessage).orElse("Unknown linstor error");
+            throw new CloudRuntimeException(errMsg);
+        }
+    }
+
+    private String checkLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        return answers.stream().filter(ApiCallRc::isError).findFirst().map(ApiCallRc::getMessage).orElse(null);
+    }
+
+    private String getDeviceName(DevelopersApi linstorApi, String rscName) throws ApiException {
+        List<ResourceWithVolumes> resources = linstorApi.viewResources(
+            Collections.emptyList(),
+            Collections.singletonList(rscName),
+            Collections.emptyList(),
+            null,
+            null,
+            null);
+        if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty())
+        {
+            s_logger.info("Linstor: Created drbd device: " + resources.get(0).getVolumes().get(0).getDevicePath());
+            return resources.get(0).getVolumes().get(0).getDevicePath();
+        } else
+        {
+            s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+            throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+        }
+    }
+
+    private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+
+        ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn();
+        final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
+        rscGrpSpawn.setResourceDefinitionName(rscName);
+        rscGrpSpawn.addVolumeSizesItem(vol.getSize() / 1024);
+
+        try
+        {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = linstorApi.resourceGroupSpawn(rscGrp, rscGrpSpawn);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String cloneResource(long csCloneId, VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        // get the cached template on this storage
+        VMTemplateStoragePoolVO tmplPoolRef = _vmTemplatePoolDao.findByPoolTemplate(
+            storagePoolVO.getId(), csCloneId, null);
+
+        if (tmplPoolRef != null) {
+            final String cloneRes = LinstorUtil.RSC_PREFIX + tmplPoolRef.getLocalDownloadPath();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+            try {
+                s_logger.debug("Clone resource definition " + cloneRes + " to " + rscName);
+                ResourceDefinitionCloneRequest cloneRequest = new ResourceDefinitionCloneRequest();
+                cloneRequest.setName(rscName);
+                ResourceDefinitionCloneStarted cloneStarted = linstorApi.resourceDefinitionClone(
+                    cloneRes, cloneRequest);
+
+                checkLinstorAnswersThrow(cloneStarted.getMessages());
+
+                if (!CloneWaiter.waitFor(linstorApi, cloneStarted)) {
+                    throw new CloudRuntimeException("Clone for resource " + rscName + " failed.");
+                }
+
+                return getDeviceName(linstorApi, rscName);
+            } catch (ApiException apiEx) {
+                s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+                throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+            }
+        } else {
+            throw new CloudRuntimeException(
+                "Unable to find Linstor resource for the following template data-object ID: " + csCloneId);
+        }
+    }
+
+    private String createResourceFromSnapshot(long csSnapshotId, String rscName, StoragePoolVO storagePoolVO) {
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+        final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        SnapshotVO snapshotVO = _snapshotDao.findById(csSnapshotId);
+        String snapName = LinstorUtil.RSC_PREFIX + snapshotVO.getUuid();
+        VolumeVO volumeVO = _volumeDao.findById(snapshotVO.getVolumeId());
+        String cloneRes = LinstorUtil.RSC_PREFIX + volumeVO.getPath();
+
+        try
+        {
+            s_logger.debug("Create new resource definition: " + rscName);
+            ResourceDefinitionCreate rdCreate = new ResourceDefinitionCreate();
+            ResourceDefinition rd = new ResourceDefinition();
+            rd.setName(rscName);
+            rd.setResourceGroupName(rscGrp);
+            rdCreate.setResourceDefinition(rd);
+            ApiCallRcList answers = linstorApi.resourceDefinitionCreate(rdCreate);
+            checkLinstorAnswersThrow(answers);
+
+            SnapshotRestore snapshotRestore = new SnapshotRestore();
+            snapshotRestore.toResource(rscName);
+
+            s_logger.debug("Create new volume definition for snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotsRestoreVolumeDefinition(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            // restore snapshot to new resource
+            s_logger.debug("Restore resource from snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotRestore(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String createVolume(VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = getCsIdForCloning(volumeInfo.getId(), "cloneOfSnapshot");
+        long csTemplateId = getCsIdForCloning(volumeInfo.getId(), "cloneOfTemplate");
+
+        if (csSnapshotId > 0) {
+            return createResourceFromSnapshot(csSnapshotId, LinstorUtil.RSC_PREFIX + volumeInfo.getUuid(), storagePoolVO);
+        } else if (csTemplateId > 0) {
+            return cloneResource(csTemplateId, volumeInfo, storagePoolVO);
+        } else {
+            return createResource(volumeInfo, storagePoolVO);
+        }
+    }
+
+    private void handleSnapshotDetails(long csSnapshotId, String name, String value) {
+        _snapshotDetailsDao.removeDetail(csSnapshotId, name);
+        SnapshotDetailsVO snapshotDetails = new SnapshotDetailsVO(csSnapshotId, name, value, false);
+        _snapshotDetailsDao.persist(snapshotDetails);
+    }
+
+    private void addTempVolumeToDb(long csSnapshotId, String tempVolumeName) {
+        // TEMP_VOLUME_ID is needed, to find which temporary resource should be deleted after copying it on agent side
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, LinstorUtil.RSC_PREFIX + tempVolumeName);
+        // the iqn will be used on the agent side to copy from, even though linstor doesn't have anything to do with IQN
+        handleSnapshotDetails(csSnapshotId, DiskTO.IQN, tempVolumeName);
+    }
+
+    private void removeTempVolumeFromDb(long csSnapshotId) {
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+        if (snapshotDetails == null || snapshotDetails.getValue() == null) {
+            throw new CloudRuntimeException(
+                "'removeTempVolumeId' should not be invoked unless " + LinstorUtil.TEMP_VOLUME_ID + " exists.");
+        }
+
+        String originalVolumeId = snapshotDetails.getValue();
+
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, originalVolumeId);
+
+        _snapshotDetailsDao.remove(snapshotDetails.getId());
+    }
+
+    private void createVolumeFromSnapshot(SnapshotInfo snapshotInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = snapshotInfo.getId();
+
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, "tempVolume");
+
+        if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("create"))
+        {
+            final String csName = "Temp-" + snapshotInfo.getUuid();
+            final String tempRscName = LinstorUtil.RSC_PREFIX + csName;
+            createResourceFromSnapshot(csSnapshotId, tempRscName, storagePoolVO);
+
+            s_logger.debug("Temp resource created: " + tempRscName);
+            addTempVolumeToDb(csSnapshotId, csName);
+        }
+        else if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("delete"))
+        {
+            snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+            deleteResourceDefinition(storagePoolVO, snapshotDetails.getValue());
+
+            s_logger.debug("Temp resource deleted: " + snapshotDetails.getValue());
+            removeTempVolumeFromDb(csSnapshotId);
+        }
+        else {
+            throw new CloudRuntimeException("Invalid state in 'createVolumeFromSnapshot(SnapshotInfo, StoragePoolVO)'");
+        }
+    }
+
+    @Override
+    public void createAsync(DataStore dataStore, DataObject vol, AsyncCompletionCallback<CreateCmdResult> callback)
+    {
+        String devPath = null;
+        String errMsg = null;
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+
+        try
+        {
+            switch (vol.getType())
+            {
+                case VOLUME:
+                    VolumeInfo volumeInfo = (VolumeInfo) vol;
+                    VolumeVO volume = _volumeDao.findById(volumeInfo.getId());
+                    s_logger.debug("createAsync - creating volume");
+                    devPath = createVolume(volumeInfo, storagePool);
+                    volume.setFolder("/dev/");
+                    volume.setPoolId(storagePool.getId());
+                    volume.setUuid(vol.getUuid());
+                    volume.setPath(vol.getUuid());
+
+                    _volumeDao.update(volume.getId(), volume);
+                    break;
+                case SNAPSHOT:
+                    s_logger.debug("createAsync - SNAPSHOT");
+                    createVolumeFromSnapshot((SnapshotInfo) vol, storagePool);
+                    break;
+                case TEMPLATE:
+                    errMsg = "creating template - not supported";
+                    s_logger.error("createAsync - " + errMsg);
+                    break;
+                default:
+                    errMsg = "Invalid DataObjectType (" + vol.getType() + ") passed to createAsync";
+                    s_logger.error(errMsg);
+            }
+        } catch (Exception ex)
+        {
+            errMsg = ex.getMessage();
+
+            s_logger.error("createAsync: " + errMsg);
+            if (callback == null)
+            {
+                throw ex;
+            }
+        }
+
+        if (callback != null)
+        {
+            CreateCmdResult result = new CreateCmdResult(devPath, new Answer(null, errMsg == null, errMsg));
+            result.setResult(errMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public void revertSnapshot(
+        SnapshotInfo snapshot,
+        SnapshotInfo snapshotOnPrimaryStore,
+        AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("Linstor: revertSnapshot");
+        final VolumeInfo volumeInfo = snapshot.getBaseVolume();
+        VolumeVO volumeVO = _volumeDao.findById(volumeInfo.getId());
+        if (volumeVO == null || volumeVO.getRemoved() != null) {
+            CommandResult commandResult = new CommandResult();
+            commandResult.setResult("The volume that the snapshot belongs to no longer exists.");
+            callback.complete(commandResult);
+            return;
+        }
+
+        String resultMsg;
+        try {
+            final StoragePool pool = (StoragePool) snapshot.getDataStore();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final String snapName = LinstorUtil.RSC_PREFIX + snapshot.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(pool.getHostAddress());
+
+            ApiCallRcList answers = linstorApi.resourceSnapshotRollback(rscName, snapName);
+            resultMsg = checkLinstorAnswers(answers);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            resultMsg = apiEx.getBestMessage();
+        }
+
+        if (callback != null)
+        {
+            CommandResult result = new CommandResult();
+            result.setResult(resultMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public boolean canCopy(DataObject srcData, DataObject destData)
+    {
+        return false;
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback)

Review comment:
       just got the following:
   ```
   Sep 08 08:52:51 cloudstack-comp2.linbit java[6215]: INFO  [resource.wrapper.LibvirtResizeVolumeCommandWrapper] (agentRequest-Handler-1:) (logid:08a8fb35) Invoking resize script to handle type NOTIFYONLY
   Sep 08 08:52:51 cloudstack-comp2.linbit java[6215]: DEBUG [resource.wrapper.LibvirtResizeVolumeCommandWrapper] (agentRequest-Handler-1:) (logid:08a8fb35) Executing: /usr/share/cloudstack-common/scripts/storage
   /qcow2/resizevolume.sh -s 10737418240 -c 9663676416 -p /dev/drbd1001 -t NOTIFYONLY -r false -v i-2-253-VM
   Sep 08 08:52:51 cloudstack-comp2.linbit java[6215]: DEBUG [resource.wrapper.LibvirtResizeVolumeCommandWrapper] (agentRequest-Handler-1:) (logid:08a8fb35) Executing while with timeout : 7200000
   Sep 08 08:52:51 cloudstack-comp2.linbit java[6215]: DEBUG [resource.wrapper.LibvirtResizeVolumeCommandWrapper] (agentRequest-Handler-1:) (logid:08a8fb35) Execution is successful.
   ```




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909168681


   yes sorry, this clone feature in Linstor took a bit longer than expected, but a first version is now released in Linstor 1.15.0-rc1.
   I also updated the plugin for the new API and did some tests and think it is ready for review now.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r674624082



##########
File path: plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
##########
@@ -0,0 +1,578 @@
+package com.cloud.hypervisor.kvm.storage;
+
+import com.linbit.linstor.api.ApiClient;
+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;
+import com.linbit.linstor.api.model.Properties;
+import com.linbit.linstor.api.model.ProviderKind;
+import com.linbit.linstor.api.model.Resource;
+import com.linbit.linstor.api.model.ResourceCreate;
+import com.linbit.linstor.api.model.ResourceDefinition;
+import com.linbit.linstor.api.model.ResourceDefinitionModify;
+import com.linbit.linstor.api.model.ResourceGroup;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
+import com.linbit.linstor.api.model.StoragePool;
+import com.linbit.linstor.api.model.VolumeDefinition;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+@StorageAdaptorInfo(storagePoolType=Storage.StoragePoolType.Linstor)
+public class LinstorStorageAdaptor implements StorageAdaptor {
+    private static final Logger s_logger = Logger.getLogger(LinstorStorageAdaptor.class);
+    private static final Map<String, KVMStoragePool> MapStorageUuidToStoragePool = new HashMap<>();
+    private final String localNodeName;
+
+    private DevelopersApi getLinstorAPI(KVMStoragePool pool) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(pool.getSourceHost());
+        return new DevelopersApi(client);
+    }
+
+    private String getLinstorRscName(String name) {
+        return "cs-" + name;
+    }
+
+    private String getHostname() {
+        // either there is already some function for that in the agent or a better way.
+        ProcessBuilder pb = new ProcessBuilder("hostname");
+        try
+        {
+            String result;
+            Process p = pb.start();
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+            StringJoiner sj = new StringJoiner(System.getProperty("line.separator"));
+            reader.lines().iterator().forEachRemaining(sj::add);
+            result = sj.toString();
+
+            p.waitFor();
+            p.destroy();
+            return result.trim();
+        } catch (IOException | InterruptedException exc) {
+            throw new CloudRuntimeException("Unable to run 'hostname' command.");
+        }
+    }
+
+    private void handleLinstorApiAnswers(ApiCallRcList answers, String excMessage) {
+        if (answers.hasError()) {
+            for (ApiCallRc answer : answers) {
+                s_logger.error(answer.getMessage());
+            }
+            throw new CloudRuntimeException(excMessage);
+        }
+    }
+
+    public LinstorStorageAdaptor() {
+        localNodeName = getHostname();
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid, boolean refreshInfo) {
+        s_logger.debug("Linstor getStoragePool: " + uuid + " -> " + refreshInfo);
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMPhysicalDisk getPhysicalDisk(String name, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: getPhysicalDisk for " + name);
+        if (name == null) {
+            return null;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try {
+            final String rscName = getLinstorRscName(name);
+
+            List<VolumeDefinition> volumeDefs = api.volumeDefinitionList(rscName, null, null);
+            final long size = volumeDefs.isEmpty() ? 0 : volumeDefs.get(0).getSizeKib() * 1024;
+
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty()) {
+                final String devPath = resources.get(0).getVolumes().get(0).getDevicePath();
+                final KVMPhysicalDisk kvmDisk = new KVMPhysicalDisk(devPath, name, pool);
+                kvmDisk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
+                kvmDisk.setSize(size);
+                kvmDisk.setVirtualSize(size);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes for " + rscName);
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiEx) {
+            s_logger.error(apiEx);
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    @Override
+    public KVMStoragePool createStoragePool(String name, String host, int port, String path, String userInfo,
+                                            Storage.StoragePoolType type)
+    {
+        s_logger.debug(String.format(
+            "Linstor createStoragePool: name: '%s', host: '%s', path: %s, userinfo: %s", name, host, path, userInfo));
+        LinstorStoragePool storagePool = new LinstorStoragePool(name, host, port, userInfo, type, this);
+
+        MapStorageUuidToStoragePool.put(name, storagePool);
+
+        return storagePool;
+    }
+
+    @Override
+    public boolean deleteStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.remove(uuid) != null;
+    }
+
+    @Override
+    public boolean deleteStoragePool(KVMStoragePool pool) {
+        return deleteStoragePool(pool.getUuid());
+    }
+
+    @Override
+    public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, QemuImg.PhysicalDiskFormat format,
+                                              Storage.ProvisioningType provisioningType, long size)
+    {
+        final String rscName = getLinstorRscName(name);
+        LinstorStoragePool lpool = (LinstorStoragePool) pool;
+        final DevelopersApi api = getLinstorAPI(pool);
+
+        try {
+            List<ResourceDefinition> definitionList = api.resourceDefinitionList(
+                Collections.singletonList(rscName), null, null, null);
+
+            if (definitionList.isEmpty()) {
+                ResourceGroupSpawn rgSpawn = new ResourceGroupSpawn();
+                rgSpawn.setResourceDefinitionName(rscName);
+                rgSpawn.addVolumeSizesItem(size / 1024); // linstor uses KiB
+
+                s_logger.debug("Linstor: Spawn resource " + rscName);
+                ApiCallRcList answers = api.resourceGroupSpawn(lpool.getResourceGroup(), rgSpawn);
+                handleLinstorApiAnswers(answers, "Linstor: Unable to spawn resource.");
+            }
+
+            // query linstor for the device path
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+
+            // TODO make avialable on node
+
+            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);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiEx) {
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    @Override
+    public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details)
+    {
+        s_logger.debug(String.format("Linstor: connectPhysicalDisk %s:%s -> %s", pool.getUuid(), volumePath, details));
+        if (volumePath == null) {
+            s_logger.warn("volumePath is null, ignoring");
+            return false;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try
+        {
+            final String rscName = getLinstorRscName(volumePath);
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.singletonList(localNodeName),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+
+            if (resources.isEmpty()) {
+                s_logger.info("Linstor: resource not created yet: " + rscName);
+                ResourceCreate rscCreate = new ResourceCreate();
+                Resource rsc = new Resource();
+                rsc.setNodeName(localNodeName);
+                rscCreate.setResource(rsc);
+                ApiCallRcList answers = api.resourceCreate(rscName, Collections.singletonList(rscCreate));
+                if (answers.hasError()) {
+                    s_logger.error("Could not create resource " + rscName);
+                    throw new CloudRuntimeException(answers.get(0).getMessage());
+                }
+                // now diskful resource should be deployed
+            }
+            // keep diskless, maybe add an option, but I'm concerned about out of space situations
+//            else {
+//                final ResourceWithVolumes rsc = resources.get(0);
+//                if (rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS)) {
+//                    ApiCallRcList answers = api.resourceToggleDiskful(rscName, localNode);
+//                    if (ApiCallChecker.hasError(answers)) {
+//                        s_logger.error("Unable to toggle disk on " + rscName);
+//                        throw new CloudRuntimeException(answers.get(0).getMessage());
+//                    }
+//                }
+//            }
+
+            // allow 2 primaries for live migration, should be removed by disconnect on the other end
+            ResourceDefinitionModify rdm = new ResourceDefinitionModify();
+            Properties props = new Properties();
+            props.put("DrbdOptions/Net/allow-two-primaries", "yes");
+            rdm.setOverrideProps(props);
+            ApiCallRcList answers = api.resourceDefinitionModify(rscName, rdm);
+            if (answers.hasError()) {
+                s_logger.error("Unable to set 'allow-two-primaries' on " + rscName);
+                throw new CloudRuntimeException(answers.get(0).getMessage());
+            }
+        } catch (ApiException apiEx) {
+            s_logger.error(apiEx);
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(String volumePath, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDisk " + pool.getUuid() + ":" + volumePath);
+        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDisk map");
+        return true;
+    }
+
+    private Optional<ResourceWithVolumes> getResourceByPath(final List<ResourceWithVolumes> resources, String path) {
+        return resources.stream()
+            .filter(rsc -> rsc.getVolumes().stream()
+                .anyMatch(v -> v.getDevicePath().equals(path)))
+            .findFirst();
+    }
+
+    /**
+     * disconnectPhysicalDiskByPath is called after e.g. a live migration.
+     * The problem is we have no idea just from the path to which linstor-controller
+     * this resource would belong to. But as it should be highly unlikely that someone
+     * uses more than one linstor-controller to manage resource on the same kvm host.
+     * We will just take the first stored storagepool.
+     */
+    @Override
+    public boolean disconnectPhysicalDiskByPath(String localPath)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDiskByPath " + localPath);
+        // get first storage pool from the map, as we don't know any better:
+        if (!MapStorageUuidToStoragePool.isEmpty())
+        {
+            String firstKey = MapStorageUuidToStoragePool.keySet().stream().findFirst().get();
+            final KVMStoragePool pool = MapStorageUuidToStoragePool.get(firstKey);
+
+            s_logger.debug("Linstor: Using storpool: " + pool.getUuid());
+            final DevelopersApi api = getLinstorAPI(pool);
+
+            try
+            {
+                List<ResourceWithVolumes> resources = api.viewResources(
+                    Collections.singletonList(localNodeName),
+                    null,
+                    null,
+                    null,
+                    null,
+                    null);
+
+                Optional<ResourceWithVolumes> rsc = getResourceByPath(resources, localPath);
+
+                if (rsc.isPresent())
+                {
+                    ResourceDefinitionModify rdm = new ResourceDefinitionModify();
+                    rdm.deleteProps(Collections.singletonList("DrbdOptions/Net/allow-two-primaries"));
+                    ApiCallRcList answers = api.resourceDefinitionModify(rsc.get().getName(), rdm);
+                    if (answers.hasError())
+                    {
+                        s_logger.error("Failed to remove 'allow-two-primaries' on " + rsc.get().getName());
+                        throw new CloudRuntimeException(answers.get(0).getMessage());
+                    }
+
+                    return true;
+                }
+                s_logger.warn("Linstor: Couldn't find resource for this path: " + localPath);
+            } catch (ApiException apiEx) {
+                s_logger.error(apiEx);
+                throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean deletePhysicalDisk(String name, KVMStoragePool pool, Storage.ImageFormat format)
+    {
+        s_logger.debug("Linstor: deletePhysicalDisk " + name);
+        final DevelopersApi api = getLinstorAPI(pool);
+
+        try {
+            final String rscName = getLinstorRscName(name);
+            s_logger.debug("Linstor: delete resource definition " + rscName);
+            ApiCallRcList answers = api.resourceDefinitionDelete(rscName);

Review comment:
       @slavkap No, you would need to delete the snapshots before you could delete the volume.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-879080479


   @blueorangutan test 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-912066432


   <b>Trillian test result (tid-1934)</b>
   Environment: kvm-centos7 (x2), Advanced Networking with Mgmt server 7
   Total time taken: 36117 seconds
   Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr4994-t1934-kvm-centos7.zip
   Smoke tests completed. 89 look OK, 0 have errors
   Only failed tests results shown below:
   
   
   Test | Result | Time (s) | Test File
   --- | --- | --- | ---
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909168681






-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-917069878


   Packaging result: :heavy_multiplication_x: el7 :heavy_multiplication_x: el8 :heavy_multiplication_x: debian :heavy_multiplication_x: suse15. SL-JID 1200


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] shwstppr commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
shwstppr commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-878883928


   @blueorangutan package


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] slavkap commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
slavkap commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r704166530



##########
File path: plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
##########
@@ -0,0 +1,720 @@
+// 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.driver;
+
+import com.linbit.linstor.api.ApiException;
+import com.linbit.linstor.api.CloneWaiter;
+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.ResourceDefinition;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted;
+import com.linbit.linstor.api.model.ResourceDefinitionCreate;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+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.VolumeDefinitionModify;
+
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.agent.api.to.DiskTO;
+import com.cloud.host.Host;
+import com.cloud.storage.ResizeVolumePayload;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.VolumeDetailVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.SnapshotDetailsDao;
+import com.cloud.storage.dao.SnapshotDetailsVO;
+import com.cloud.storage.dao.VMTemplatePoolDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.storage.command.CreateObjectAnswer;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
+import org.apache.cloudstack.storage.volume.VolumeObject;
+import org.apache.log4j.Logger;
+
+public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver {
+    private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class);
+    @Inject private PrimaryDataStoreDao _storagePoolDao;
+    @Inject private VolumeDao _volumeDao;
+    @Inject private VolumeDetailsDao _volumeDetailsDao;
+    @Inject private VMTemplatePoolDao _vmTemplatePoolDao;
+    @Inject private SnapshotDao _snapshotDao;
+    @Inject private SnapshotDetailsDao _snapshotDetailsDao;
+
+    public LinstorPrimaryDataStoreDriverImpl()
+    {
+    }
+
+    @Override
+    public Map<String, String> getCapabilities()
+    {
+        Map<String, String> mapCapabilities = new HashMap<>();
+
+        // Linstor will be restricted to only run on LVM-THIN and ZFS storage pools with ACS
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString(), Boolean.TRUE.toString());
+
+        // fetch if lvm-thin or ZFS
+        mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        // CAN_CREATE_VOLUME_FROM_SNAPSHOT see note from CAN_CREATE_VOLUME_FROM_VOLUME
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+        mapCapabilities.put(DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        return mapCapabilities;
+    }
+
+    @Override
+    public DataTO getTO(DataObject data)
+    {
+        return null;
+    }
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store)
+    {
+        return null;
+    }
+
+    @Override
+    public ChapInfo getChapInfo(DataObject dataObject)
+    {
+        return null;
+    }
+
+    @Override
+    public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+        return false;
+    }
+
+    @Override
+    public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+    }
+
+    @Override
+    public long getUsedBytes(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getUsedIops(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool)
+    {
+        return dataObject.getSize();
+    }
+
+    @Override
+    public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    private String getSnapshotName(String snapshotUuid) {
+        return LinstorUtil.RSC_PREFIX + snapshotUuid;
+    }
+
+    private void deleteResourceDefinition(StoragePoolVO storagePoolVO, String rscDefName)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceDefinitionDelete(rscDefName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete resource definition: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private void deleteSnapshot(@Nonnull DataStore dataStore, @Nonnull String rscDefName, @Nonnull String snapshotName)
+    {
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceSnapshotDelete(rscDefName, snapshotName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete snapshot: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private long getCsIdForCloning(long volumeId, String cloneOf) {
+        VolumeDetailVO volumeDetail = _volumeDetailsDao.findDetail(volumeId, cloneOf);
+
+        if (volumeDetail != null && volumeDetail.getValue() != null) {
+            return Long.parseLong(volumeDetail.getValue());
+        }
+
+        return Long.MIN_VALUE;
+    }
+
+    @Override
+    public void deleteAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("deleteAsync: " + dataObject.getType() + ";" + dataObject.getUuid());
+        String errMsg = null;
+
+        final long storagePoolId = dataStore.getId();
+        final StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
+
+        switch (dataObject.getType()) {
+            case VOLUME:
+            {
+                final VolumeInfo volumeInfo = (VolumeInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getPath();
+                deleteResourceDefinition(storagePool, rscName);
+
+                long usedBytes = storagePool.getUsedBytes();
+                long capacityIops = storagePool.getCapacityIops();
+
+                usedBytes -= volumeInfo.getSize();
+                if (volumeInfo.getMaxIops() != null)
+                    capacityIops += volumeInfo.getMaxIops();
+
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                storagePool.setCapacityIops(Math.max(0, capacityIops));
+
+                _storagePoolDao.update(storagePoolId, storagePool);
+            }
+                break;
+            case SNAPSHOT:
+                final SnapshotInfo snapshotInfo = (SnapshotInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + snapshotInfo.getBaseVolume().getPath();
+                deleteSnapshot(dataStore, rscName, getSnapshotName(snapshotInfo.getUuid()));
+                long usedBytes = storagePool.getUsedBytes() - snapshotInfo.getSize();
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                _storagePoolDao.update(storagePoolId, storagePool);
+                break;
+            default:
+                errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync";
+                s_logger.error(errMsg);
+        }
+
+        if (callback != null) {
+            CommandResult result = new CommandResult();
+            result.setResult(errMsg);
+
+            callback.complete(result);
+        }
+    }
+
+    private void logLinstorAnswer(@Nonnull ApiCallRc answer) {
+        if (answer.isError()) {
+            s_logger.error(answer.getMessage());
+        } else if (answer.isWarning()) {
+            s_logger.warn(answer.getMessage());
+        } else if (answer.isInfo()) {
+            s_logger.info(answer.getMessage());
+        }
+    }
+
+    private void logLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        answers.forEach(this::logLinstorAnswer);
+    }
+
+    private void checkLinstorAnswersThrow(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        if (answers.hasError())
+        {
+            String errMsg = answers.stream()
+                .filter(ApiCallRc::isError)
+                .findFirst()
+                .map(ApiCallRc::getMessage).orElse("Unknown linstor error");
+            throw new CloudRuntimeException(errMsg);
+        }
+    }
+
+    private String checkLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        return answers.stream().filter(ApiCallRc::isError).findFirst().map(ApiCallRc::getMessage).orElse(null);
+    }
+
+    private String getDeviceName(DevelopersApi linstorApi, String rscName) throws ApiException {
+        List<ResourceWithVolumes> resources = linstorApi.viewResources(
+            Collections.emptyList(),
+            Collections.singletonList(rscName),
+            Collections.emptyList(),
+            null,
+            null,
+            null);
+        if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty())
+        {
+            s_logger.info("Linstor: Created drbd device: " + resources.get(0).getVolumes().get(0).getDevicePath());
+            return resources.get(0).getVolumes().get(0).getDevicePath();
+        } else
+        {
+            s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+            throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+        }
+    }
+
+    private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+
+        ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn();
+        final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
+        rscGrpSpawn.setResourceDefinitionName(rscName);
+        rscGrpSpawn.addVolumeSizesItem(vol.getSize() / 1024);
+
+        try
+        {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = linstorApi.resourceGroupSpawn(rscGrp, rscGrpSpawn);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String cloneResource(long csCloneId, VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        // get the cached template on this storage
+        VMTemplateStoragePoolVO tmplPoolRef = _vmTemplatePoolDao.findByPoolTemplate(
+            storagePoolVO.getId(), csCloneId, null);
+
+        if (tmplPoolRef != null) {
+            final String cloneRes = LinstorUtil.RSC_PREFIX + tmplPoolRef.getLocalDownloadPath();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+            try {
+                s_logger.debug("Clone resource definition " + cloneRes + " to " + rscName);
+                ResourceDefinitionCloneRequest cloneRequest = new ResourceDefinitionCloneRequest();
+                cloneRequest.setName(rscName);
+                ResourceDefinitionCloneStarted cloneStarted = linstorApi.resourceDefinitionClone(
+                    cloneRes, cloneRequest);
+
+                checkLinstorAnswersThrow(cloneStarted.getMessages());
+
+                if (!CloneWaiter.waitFor(linstorApi, cloneStarted)) {
+                    throw new CloudRuntimeException("Clone for resource " + rscName + " failed.");
+                }
+
+                return getDeviceName(linstorApi, rscName);
+            } catch (ApiException apiEx) {
+                s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+                throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+            }
+        } else {
+            throw new CloudRuntimeException(
+                "Unable to find Linstor resource for the following template data-object ID: " + csCloneId);
+        }
+    }
+
+    private String createResourceFromSnapshot(long csSnapshotId, String rscName, StoragePoolVO storagePoolVO) {
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+        final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        SnapshotVO snapshotVO = _snapshotDao.findById(csSnapshotId);
+        String snapName = LinstorUtil.RSC_PREFIX + snapshotVO.getUuid();
+        VolumeVO volumeVO = _volumeDao.findById(snapshotVO.getVolumeId());
+        String cloneRes = LinstorUtil.RSC_PREFIX + volumeVO.getPath();
+
+        try
+        {
+            s_logger.debug("Create new resource definition: " + rscName);
+            ResourceDefinitionCreate rdCreate = new ResourceDefinitionCreate();
+            ResourceDefinition rd = new ResourceDefinition();
+            rd.setName(rscName);
+            rd.setResourceGroupName(rscGrp);
+            rdCreate.setResourceDefinition(rd);
+            ApiCallRcList answers = linstorApi.resourceDefinitionCreate(rdCreate);
+            checkLinstorAnswersThrow(answers);
+
+            SnapshotRestore snapshotRestore = new SnapshotRestore();
+            snapshotRestore.toResource(rscName);
+
+            s_logger.debug("Create new volume definition for snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotsRestoreVolumeDefinition(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            // restore snapshot to new resource
+            s_logger.debug("Restore resource from snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotRestore(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String createVolume(VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = getCsIdForCloning(volumeInfo.getId(), "cloneOfSnapshot");
+        long csTemplateId = getCsIdForCloning(volumeInfo.getId(), "cloneOfTemplate");
+
+        if (csSnapshotId > 0) {
+            return createResourceFromSnapshot(csSnapshotId, LinstorUtil.RSC_PREFIX + volumeInfo.getUuid(), storagePoolVO);
+        } else if (csTemplateId > 0) {
+            return cloneResource(csTemplateId, volumeInfo, storagePoolVO);
+        } else {
+            return createResource(volumeInfo, storagePoolVO);
+        }
+    }
+
+    private void handleSnapshotDetails(long csSnapshotId, String name, String value) {
+        _snapshotDetailsDao.removeDetail(csSnapshotId, name);
+        SnapshotDetailsVO snapshotDetails = new SnapshotDetailsVO(csSnapshotId, name, value, false);
+        _snapshotDetailsDao.persist(snapshotDetails);
+    }
+
+    private void addTempVolumeToDb(long csSnapshotId, String tempVolumeName) {
+        // TEMP_VOLUME_ID is needed, to find which temporary resource should be deleted after copying it on agent side
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, LinstorUtil.RSC_PREFIX + tempVolumeName);
+        // the iqn will be used on the agent side to copy from, even though linstor doesn't have anything to do with IQN
+        handleSnapshotDetails(csSnapshotId, DiskTO.IQN, tempVolumeName);
+    }
+
+    private void removeTempVolumeFromDb(long csSnapshotId) {
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+        if (snapshotDetails == null || snapshotDetails.getValue() == null) {
+            throw new CloudRuntimeException(
+                "'removeTempVolumeId' should not be invoked unless " + LinstorUtil.TEMP_VOLUME_ID + " exists.");
+        }
+
+        String originalVolumeId = snapshotDetails.getValue();
+
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, originalVolumeId);
+
+        _snapshotDetailsDao.remove(snapshotDetails.getId());
+    }
+
+    private void createVolumeFromSnapshot(SnapshotInfo snapshotInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = snapshotInfo.getId();
+
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, "tempVolume");
+
+        if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("create"))
+        {
+            final String csName = "Temp-" + snapshotInfo.getUuid();
+            final String tempRscName = LinstorUtil.RSC_PREFIX + csName;
+            createResourceFromSnapshot(csSnapshotId, tempRscName, storagePoolVO);
+
+            s_logger.debug("Temp resource created: " + tempRscName);
+            addTempVolumeToDb(csSnapshotId, csName);
+        }
+        else if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("delete"))
+        {
+            snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+            deleteResourceDefinition(storagePoolVO, snapshotDetails.getValue());
+
+            s_logger.debug("Temp resource deleted: " + snapshotDetails.getValue());
+            removeTempVolumeFromDb(csSnapshotId);
+        }
+        else {
+            throw new CloudRuntimeException("Invalid state in 'createVolumeFromSnapshot(SnapshotInfo, StoragePoolVO)'");
+        }
+    }
+
+    @Override
+    public void createAsync(DataStore dataStore, DataObject vol, AsyncCompletionCallback<CreateCmdResult> callback)
+    {
+        String devPath = null;
+        String errMsg = null;
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+
+        try
+        {
+            switch (vol.getType())
+            {
+                case VOLUME:
+                    VolumeInfo volumeInfo = (VolumeInfo) vol;
+                    VolumeVO volume = _volumeDao.findById(volumeInfo.getId());
+                    s_logger.debug("createAsync - creating volume");
+                    devPath = createVolume(volumeInfo, storagePool);
+                    volume.setFolder("/dev/");
+                    volume.setPoolId(storagePool.getId());
+                    volume.setUuid(vol.getUuid());
+                    volume.setPath(vol.getUuid());
+
+                    _volumeDao.update(volume.getId(), volume);
+                    break;
+                case SNAPSHOT:
+                    s_logger.debug("createAsync - SNAPSHOT");
+                    createVolumeFromSnapshot((SnapshotInfo) vol, storagePool);
+                    break;
+                case TEMPLATE:
+                    errMsg = "creating template - not supported";
+                    s_logger.error("createAsync - " + errMsg);
+                    break;
+                default:
+                    errMsg = "Invalid DataObjectType (" + vol.getType() + ") passed to createAsync";
+                    s_logger.error(errMsg);
+            }
+        } catch (Exception ex)
+        {
+            errMsg = ex.getMessage();
+
+            s_logger.error("createAsync: " + errMsg);
+            if (callback == null)
+            {
+                throw ex;
+            }
+        }
+
+        if (callback != null)
+        {
+            CreateCmdResult result = new CreateCmdResult(devPath, new Answer(null, errMsg == null, errMsg));
+            result.setResult(errMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public void revertSnapshot(
+        SnapshotInfo snapshot,
+        SnapshotInfo snapshotOnPrimaryStore,
+        AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("Linstor: revertSnapshot");
+        final VolumeInfo volumeInfo = snapshot.getBaseVolume();
+        VolumeVO volumeVO = _volumeDao.findById(volumeInfo.getId());
+        if (volumeVO == null || volumeVO.getRemoved() != null) {
+            CommandResult commandResult = new CommandResult();
+            commandResult.setResult("The volume that the snapshot belongs to no longer exists.");
+            callback.complete(commandResult);
+            return;
+        }
+
+        String resultMsg;
+        try {
+            final StoragePool pool = (StoragePool) snapshot.getDataStore();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final String snapName = LinstorUtil.RSC_PREFIX + snapshot.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(pool.getHostAddress());
+
+            ApiCallRcList answers = linstorApi.resourceSnapshotRollback(rscName, snapName);
+            resultMsg = checkLinstorAnswers(answers);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            resultMsg = apiEx.getBestMessage();
+        }
+
+        if (callback != null)
+        {
+            CommandResult result = new CommandResult();
+            result.setResult(resultMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public boolean canCopy(DataObject srcData, DataObject destData)
+    {
+        return false;
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback)

Review comment:
       @rp- is there any output when 
   `/usr/share/cloudstack-common/scripts/storage/qcow2/resizevolume.sh -s
    newSize -c currentSize -p volumePath -t NOTIFYONLY -r shrink -v vmName` is executed?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-919767628


   @blueorangutan test centos7 xenserver-71


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] nvazquez commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
nvazquez commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-919693093


   @blueorangutan test


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] nvazquez commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
nvazquez commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-957031297


   Hi @rp- @Philipp-Reisner can you please advise on the reported issue? Could be a configuration issue or a bug in CloudStack that needs fixing?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] nvazquez commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
nvazquez commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-957031297


   Hi @rp- @Philipp-Reisner can you please advise on the reported issue? Could be a configuration issue or a bug in CloudStack that needs fixing?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-957147583


   @nvazquez I'm on it via a different communication channel
   
   But I suspect a configuration problem, I'll report back here what solved the case.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] soptecsn commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
soptecsn commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-956585899


   We are trying to deploy linstor driver for primary storage and it is not wokring. We have configured 3 linstor nodes with ZFS, ACS 4.16
   
   First we get an error that linstor was not installed in the host, so we install it and the error disappear, we think is not correct that Host has to be installed with the software because they just to be a "client". But there aren't any documentation about this.
   
   Then, when that error disappear, we try to create a VM and the log in the host show this:
   
   INFO
    [kvm.storage.LinstorStorageAdaptor] (agentRequest-Handler-1:null)
    (logid:1fb3773c)
    Linstor: Created drbd device: /dev/zvol/Linstor-ST04/cs-c3bbbcbb-142a-4f28-bf1b-fd0748c7a6f7_00000
    ERROR [kvm.storage.LinstorStorageAdaptor] (agentRequest-Handler-1:null)
    (logid:1fb3773c)
    org.apache.cloudstack.utils.qemu.QemuImgException: qemu-img:
    /dev/zvol/Linstor-ST04/cs-c3bbbcbb-142a-4f28-bf1b-fd0748c7a6f7_00000:
    error while
    converting raw:
    Could not create file:
    No such file
   or
    directory
   
   When we go to the linstor dashboard, we see that Linstor creates a Resource Definition, then a resource into and finally the volumen, until this moment it all looking good, but inmediatly, deletes all resources created (resource definition, resource and volume).
   
   Linstor and Cloudstack are connecting fine when we add the primary storage.
   
   Error from the linstor side:
   
    
           ERROR REPORT 617B1236-00000-003014
   
   ============================================================
   
   Application:                        LINBIT® LINSTOR
   Module:                             Controller
   Version:                            1.15.0
   Build ID:                           7a46abd8becd8027357d06f5990a272224a71e81
   Build time:                         2021-09-23T06:21:28+00:00
   Error time:                         2021-11-01 20:59:15
   Node:                               linstor-st01
   Peer:                               RestClient(10.0.10.26; 'Swagger-Codegen/1.0.0/java')
   
   ============================================================
   
   Reported error:
   ===============
   
   Category:                           RuntimeException
   Class name:                         ApiRcException
   Class canonical name:               com.linbit.linstor.core.apicallhandler.response.ApiRcException
   Generated at:                       Method 'loadRscDfn', Source file 'CtrlApiDataLoader.java', Line #188
   
   Error message:                      Resource definition 'cs-c3bbbcbb-142a-4f28-bf1b-fd0748c7a6f7' not found.
   
   Error context:
       Resource definition 'cs-c3bbbcbb-142a-4f28-bf1b-fd0748c7a6f7' not found.
   
   Asynchronous stage backtrace:
   
       Error has been observed at the following site(s):
       	|_ checkpoint ⇢ Clone resource-definition
       Stack trace:
   
   Call backtrace:
   
       Method                                   Native Class:Line number
       loadRscDfn                               N      com.linbit.linstor.core.apicallhandler.controller.CtrlApiDataLoader:188
   
   Suppressed exception 1 of 1:
   ===============
   Category:                           RuntimeException
   Class name:                         OnAssemblyException
   Class canonical name:               reactor.core.publisher.FluxOnAssembly.OnAssemblyException
   Generated at:                       Method 'loadRscDfn', Source file 'CtrlApiDataLoader.java', Line #188
   
   Error message:                      
   Error has been observed at the following site(s):
   	|_ checkpoint ⇢ Clone resource-definition
   Stack trace:
   
   Error context:
       Resource definition 'cs-c3bbbcbb-142a-4f28-bf1b-fd0748c7a6f7' not found.
   
   Call backtrace:
   
       Method                                   Native Class:Line number
       loadRscDfn                               N      com.linbit.linstor.core.apicallhandler.controller.CtrlApiDataLoader:188
       loadRscDfn                               N      com.linbit.linstor.core.apicallhandler.controller.CtrlApiDataLoader:162
       cloneRscDfnInTransaction                 N      com.linbit.linstor.core.apicallhandler.controller.CtrlRscDfnApiCallHandler:850
       lambda$cloneRscDfn$6                     N      com.linbit.linstor.core.apicallhandler.controller.CtrlRscDfnApiCallHandler:723
       doInScope                                N      com.linbit.linstor.core.apicallhandler.ScopeRunner:147
       lambda$fluxInScope$0                     N      com.linbit.linstor.core.apicallhandler.ScopeRunner:75
       call                                     N      reactor.core.publisher.MonoCallable:91
       trySubscribeScalarMap                    N      reactor.core.publisher.FluxFlatMap:126
       subscribeOrReturn                        N      reactor.core.publisher.MonoFlatMapMany:49
       subscribe                                N      reactor.core.publisher.Flux:8343
       onNext                                   N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain:188
       request                                  N      reactor.core.publisher.Operators$ScalarSubscription:2344
       onSubscribe                              N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain:134
       subscribe                                N      reactor.core.publisher.MonoCurrentContext:35
       subscribe                                N      reactor.core.publisher.Flux:8357
       onNext                                   N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain:188
       request                                  N      reactor.core.publisher.Operators$ScalarSubscription:2344
       onSubscribe                              N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain:134
       subscribe                                N      reactor.core.publisher.MonoCurrentContext:35
       subscribe                                N      reactor.core.publisher.Flux:8357
       onNext                                   N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain:188
       onNext                                   N      reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber:121
       complete                                 N      reactor.core.publisher.Operators$MonoSubscriber:1782
       onComplete                               N      reactor.core.publisher.MonoCollect$CollectSubscriber:152
       onComplete                               N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner:252
       checkTerminated                          N      reactor.core.publisher.FluxFlatMap$FlatMapMain:838
       drainLoop                                N      reactor.core.publisher.FluxFlatMap$FlatMapMain:600
       drain                                    N      reactor.core.publisher.FluxFlatMap$FlatMapMain:580
       onComplete                               N      reactor.core.publisher.FluxFlatMap$FlatMapMain:457
       checkTerminated                          N      reactor.core.publisher.FluxFlatMap$FlatMapMain:838
       drainLoop                                N      reactor.core.publisher.FluxFlatMap$FlatMapMain:600
       innerComplete                            N      reactor.core.publisher.FluxFlatMap$FlatMapMain:909
       onComplete                               N      reactor.core.publisher.FluxFlatMap$FlatMapInner:1013
       onComplete                               N      reactor.core.publisher.FluxMap$MapSubscriber:136
       onComplete                               N      reactor.core.publisher.Operators$MultiSubscriptionSubscriber:2016
       onComplete                               N      reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber:78
       complete                                 N      reactor.core.publisher.FluxCreate$BaseSink:438
       drain                                    N      reactor.core.publisher.FluxCreate$BufferAsyncSink:784
       complete                                 N      reactor.core.publisher.FluxCreate$BufferAsyncSink:732
       drainLoop                                N      reactor.core.publisher.FluxCreate$SerializedSink:239
       drain                                    N      reactor.core.publisher.FluxCreate$SerializedSink:205
       complete                                 N      reactor.core.publisher.FluxCreate$SerializedSink:196
       apiCallComplete                          N      com.linbit.linstor.netcom.TcpConnectorPeer:455
       handleComplete                           N      com.linbit.linstor.proto.CommonMessageProcessor:363
       handleDataMessage                        N      com.linbit.linstor.proto.CommonMessageProcessor:287
       doProcessInOrderMessage                  N      com.linbit.linstor.proto.CommonMessageProcessor:235
       lambda$doProcessMessage$3                N      com.linbit.linstor.proto.CommonMessageProcessor:220
       subscribe                                N      reactor.core.publisher.FluxDefer:46
       subscribe                                N      reactor.core.publisher.Flux:8357
       onNext                                   N      reactor.core.publisher.FluxFlatMap$FlatMapMain:418
       drainAsync                               N      reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber:414
       drain                                    N      reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber:679
       onNext                                   N      reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber:243
       drainFused                               N      reactor.core.publisher.UnicastProcessor:286
       drain                                    N      reactor.core.publisher.UnicastProcessor:329
       onNext                                   N      reactor.core.publisher.UnicastProcessor:408
       next                                     N      reactor.core.publisher.FluxCreate$IgnoreSink:618
       drainLoop                                N      reactor.core.publisher.FluxCreate$SerializedSink:248
       next                                     N      reactor.core.publisher.FluxCreate$SerializedSink:168
       processInOrder                           N      com.linbit.linstor.netcom.TcpConnectorPeer:373
       doProcessMessage                         N      com.linbit.linstor.proto.CommonMessageProcessor:218
       lambda$processMessage$2                  N      com.linbit.linstor.proto.CommonMessageProcessor:164
       onNext                                   N      reactor.core.publisher.FluxPeek$PeekSubscriber:177
       runAsync                                 N      reactor.core.publisher.FluxPublishOn$PublishOnSubscriber:439
       run                                      N      reactor.core.publisher.FluxPublishOn$PublishOnSubscriber:526
       call                                     N      reactor.core.scheduler.WorkerTask:84
       call                                     N      reactor.core.scheduler.WorkerTask:37
       run                                      N      java.util.concurrent.FutureTask:264
       run                                      N      java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask:304
       runWorker                                N      java.util.concurrent.ThreadPoolExecutor:1128
       run                                      N      java.util.concurrent.ThreadPoolExecutor$Worker:628
       run                                      N      java.lang.Thread:829
   
   
   END OF ERROR REPORT.
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-841068309


   > @rp- Please check if Linstor storage plugin supports all the functionalities mentioned in the cwiki. Can you add some storage references in cwiki ?
   
   What do you mean with "storage references" ?
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909346532


   @blueorangutan test 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] GutoVeronezi commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
GutoVeronezi commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r629444059



##########
File path: plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
##########
@@ -0,0 +1,566 @@
+package com.cloud.hypervisor.kvm.storage;
+
+import com.linbit.linstor.api.ApiCallChecker;
+import com.linbit.linstor.api.ApiClient;
+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;
+import com.linbit.linstor.api.model.Properties;
+import com.linbit.linstor.api.model.ProviderKind;
+import com.linbit.linstor.api.model.Resource;
+import com.linbit.linstor.api.model.ResourceCreate;
+import com.linbit.linstor.api.model.ResourceDefinitionModify;
+import com.linbit.linstor.api.model.ResourceGroup;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
+import com.linbit.linstor.api.model.StoragePool;
+import com.linbit.linstor.api.model.VolumeDefinition;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+@StorageAdaptorInfo(storagePoolType=Storage.StoragePoolType.Linstor)
+public class LinstorStorageAdaptor implements StorageAdaptor {
+    private static final Logger s_logger = Logger.getLogger(LinstorStorageAdaptor.class);
+    private static final Map<String, KVMStoragePool> MapStorageUuidToStoragePool = new HashMap<>();
+
+    private DevelopersApi getLinstorAPI(KVMStoragePool pool) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(pool.getSourceHost());
+        return new DevelopersApi(client);
+    }
+
+    private String getLinstorRscName(String name) {
+        return "cs-" + name;
+    }
+
+    private String getHostname() {
+        // either there is already some function for that in the agent or a better way.
+        ProcessBuilder pb = new ProcessBuilder("hostname");
+        try
+        {
+            String result;
+            Process p = pb.start();
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+            StringJoiner sj = new StringJoiner(System.getProperty("line.separator"));
+            reader.lines().iterator().forEachRemaining(sj::add);
+            result = sj.toString();

Review comment:
       As `result` is first used here, you could declare it here too.

##########
File path: plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
##########
@@ -0,0 +1,566 @@
+package com.cloud.hypervisor.kvm.storage;
+
+import com.linbit.linstor.api.ApiCallChecker;
+import com.linbit.linstor.api.ApiClient;
+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;
+import com.linbit.linstor.api.model.Properties;
+import com.linbit.linstor.api.model.ProviderKind;
+import com.linbit.linstor.api.model.Resource;
+import com.linbit.linstor.api.model.ResourceCreate;
+import com.linbit.linstor.api.model.ResourceDefinitionModify;
+import com.linbit.linstor.api.model.ResourceGroup;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
+import com.linbit.linstor.api.model.StoragePool;
+import com.linbit.linstor.api.model.VolumeDefinition;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+@StorageAdaptorInfo(storagePoolType=Storage.StoragePoolType.Linstor)
+public class LinstorStorageAdaptor implements StorageAdaptor {
+    private static final Logger s_logger = Logger.getLogger(LinstorStorageAdaptor.class);
+    private static final Map<String, KVMStoragePool> MapStorageUuidToStoragePool = new HashMap<>();
+
+    private DevelopersApi getLinstorAPI(KVMStoragePool pool) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(pool.getSourceHost());
+        return new DevelopersApi(client);
+    }
+
+    private String getLinstorRscName(String name) {
+        return "cs-" + name;
+    }
+
+    private String getHostname() {
+        // either there is already some function for that in the agent or a better way.
+        ProcessBuilder pb = new ProcessBuilder("hostname");
+        try
+        {
+            String result;
+            Process p = pb.start();
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+            StringJoiner sj = new StringJoiner(System.getProperty("line.separator"));
+            reader.lines().iterator().forEachRemaining(sj::add);
+            result = sj.toString();
+
+            p.waitFor();
+            p.destroy();
+            return result;
+        } catch (IOException | InterruptedException exc) {
+            throw new CloudRuntimeException("Unable to run hostname command.");

Review comment:
       You can pass the exception as parameter of `CloudRuntimeException`.

##########
File path: plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
##########
@@ -0,0 +1,566 @@
+package com.cloud.hypervisor.kvm.storage;
+
+import com.linbit.linstor.api.ApiCallChecker;
+import com.linbit.linstor.api.ApiClient;
+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;
+import com.linbit.linstor.api.model.Properties;
+import com.linbit.linstor.api.model.ProviderKind;
+import com.linbit.linstor.api.model.Resource;
+import com.linbit.linstor.api.model.ResourceCreate;
+import com.linbit.linstor.api.model.ResourceDefinitionModify;
+import com.linbit.linstor.api.model.ResourceGroup;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
+import com.linbit.linstor.api.model.StoragePool;
+import com.linbit.linstor.api.model.VolumeDefinition;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+@StorageAdaptorInfo(storagePoolType=Storage.StoragePoolType.Linstor)
+public class LinstorStorageAdaptor implements StorageAdaptor {
+    private static final Logger s_logger = Logger.getLogger(LinstorStorageAdaptor.class);
+    private static final Map<String, KVMStoragePool> MapStorageUuidToStoragePool = new HashMap<>();
+
+    private DevelopersApi getLinstorAPI(KVMStoragePool pool) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(pool.getSourceHost());
+        return new DevelopersApi(client);
+    }
+
+    private String getLinstorRscName(String name) {
+        return "cs-" + name;
+    }
+
+    private String getHostname() {
+        // either there is already some function for that in the agent or a better way.
+        ProcessBuilder pb = new ProcessBuilder("hostname");
+        try
+        {
+            String result;
+            Process p = pb.start();
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+            StringJoiner sj = new StringJoiner(System.getProperty("line.separator"));
+            reader.lines().iterator().forEachRemaining(sj::add);
+            result = sj.toString();
+
+            p.waitFor();
+            p.destroy();
+            return result;
+        } catch (IOException | InterruptedException exc) {
+            throw new CloudRuntimeException("Unable to run hostname command.");
+        }
+    }
+
+    private void handleLinstorApiAnswers(ApiCallRcList answers, String excMessage) {
+        if (ApiCallChecker.hasError(answers)) {
+            for (ApiCallRc answer : answers) {
+                s_logger.error(answer.getMessage());
+            }
+            throw new CloudRuntimeException(excMessage);
+        }
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid, boolean refreshInfo) {
+        s_logger.debug("Linstor getStoragePool: " + uuid + " -> " + refreshInfo);
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMPhysicalDisk getPhysicalDisk(String name, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: getPhysicalDisk for " + name);
+        if (name == null) {
+            return null;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try {
+            final String rscName = getLinstorRscName(name);
+
+            List<VolumeDefinition> volumeDefs = api.volumeDefinitionList(rscName, null, null);
+            final long size = volumeDefs.isEmpty() ? 0 : volumeDefs.get(0).getSizeKib() * 1024;
+
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty()) {
+                final String devPath = resources.get(0).getVolumes().get(0).getDevicePath();
+                final KVMPhysicalDisk kvmDisk = new KVMPhysicalDisk(devPath, name, pool);
+                kvmDisk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
+                kvmDisk.setSize(size);
+                kvmDisk.setVirtualSize(size);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes for " + rscName);
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiExc) {
+            s_logger.error(apiExc);
+            throw new CloudRuntimeException(apiExc);
+        }
+    }
+
+    @Override
+    public KVMStoragePool createStoragePool(String name, String host, int port, String path, String userInfo,
+                                            Storage.StoragePoolType type)
+    {
+        s_logger.debug(String.format(
+            "Linstor createStoragePool: name: '%s', host: '%s', path: %s, userinfo: %s", name, host, path, userInfo));
+        LinstorStoragePool storagePool = new LinstorStoragePool(name, host, port, userInfo, type, this);
+
+        MapStorageUuidToStoragePool.put(name, storagePool);
+
+        return storagePool;
+    }
+
+    @Override
+    public boolean deleteStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.remove(uuid) != null;
+    }
+
+    @Override
+    public boolean deleteStoragePool(KVMStoragePool pool) {
+        return deleteStoragePool(pool.getUuid());
+    }
+
+    @Override
+    public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, QemuImg.PhysicalDiskFormat format,
+                                              Storage.ProvisioningType provisioningType, long size)
+    {
+        LinstorStoragePool lpool = (LinstorStoragePool) pool;
+        final DevelopersApi api = getLinstorAPI(pool);
+        ResourceGroupSpawn rgSpawn = new ResourceGroupSpawn();
+        final String rscName = getLinstorRscName(name);
+        rgSpawn.setResourceDefinitionName(rscName);
+        rgSpawn.addVolumeSizesItem(size / 1024); // linstor uses KiB
+        try {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = api.resourceGroupSpawn(lpool.getResourceGroup(), rgSpawn);
+            handleLinstorApiAnswers(answers, "Linstor: Unable to spawn resource.");
+
+            // query linstor for the device path
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            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);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiExc) {
+            throw new CloudRuntimeException(apiExc);
+        }
+    }
+
+    @Override
+    public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details)
+    {
+        s_logger.debug(String.format("Linstor: connectPhysicalDisk %s:%s -> %s", pool.getUuid(), volumePath, details));
+        if (volumePath == null) {
+            s_logger.warn("volumePath is null, ignoring");
+            return false;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try
+        {
+            final String rscName = getLinstorRscName(volumePath);
+            final String localNode = getHostname();
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.singletonList(localNode),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+
+            if (resources.isEmpty()) {
+                s_logger.info("Linstor: resource not created yet: " + rscName);
+                ResourceCreate rscCreate = new ResourceCreate();
+                Resource rsc = new Resource();
+                rsc.setNodeName(localNode);
+                rscCreate.setResource(rsc);
+                ApiCallRcList answers = api.resourceCreate(rscName, Collections.singletonList(rscCreate));
+                if (ApiCallChecker.hasError(answers)) {
+                    s_logger.error("Could not create resource " + rscName);
+                    throw new CloudRuntimeException(answers.get(0).getMessage());
+                }
+                // now diskful resource should be deployed
+            }
+            // keep diskless, maybe add an option, but I'm concerned about out of space situations
+//            else {
+//                final ResourceWithVolumes rsc = resources.get(0);
+//                if (rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS)) {
+//                    ApiCallRcList answers = api.resourceToggleDiskful(rscName, localNode);
+//                    if (ApiCallChecker.hasError(answers)) {
+//                        s_logger.error("Unable to toggle disk on " + rscName);
+//                        throw new CloudRuntimeException(answers.get(0).getMessage());
+//                    }
+//                }
+//            }
+
+            // allow 2 primaries for live migration, should be removed by disconnect on the other end
+            ResourceDefinitionModify rdm = new ResourceDefinitionModify();
+            Properties props = new Properties();
+            props.put("DrbdOptions/Net/allow-two-primaries", "yes");
+            rdm.setOverrideProps(props);
+            ApiCallRcList answers = api.resourceDefinitionModify(rscName, rdm);
+            if (ApiCallChecker.hasError(answers)) {
+                s_logger.error("Unable to set 'allow-two-primaries' on " + rscName);
+                throw new CloudRuntimeException(answers.get(0).getMessage());
+            }
+        } catch (ApiException apiExc) {
+            s_logger.error(apiExc);
+            throw new CloudRuntimeException(apiExc.getMessage());
+        }
+        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(String volumePath, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDisk " + pool.getUuid() + ":" + volumePath);
+        // TODO remove diskless/diskful on node

Review comment:
       Would be better to register TODO in issues instead of comments in the code.

##########
File path: plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
##########
@@ -0,0 +1,566 @@
+package com.cloud.hypervisor.kvm.storage;
+
+import com.linbit.linstor.api.ApiCallChecker;
+import com.linbit.linstor.api.ApiClient;
+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;
+import com.linbit.linstor.api.model.Properties;
+import com.linbit.linstor.api.model.ProviderKind;
+import com.linbit.linstor.api.model.Resource;
+import com.linbit.linstor.api.model.ResourceCreate;
+import com.linbit.linstor.api.model.ResourceDefinitionModify;
+import com.linbit.linstor.api.model.ResourceGroup;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
+import com.linbit.linstor.api.model.StoragePool;
+import com.linbit.linstor.api.model.VolumeDefinition;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+@StorageAdaptorInfo(storagePoolType=Storage.StoragePoolType.Linstor)
+public class LinstorStorageAdaptor implements StorageAdaptor {
+    private static final Logger s_logger = Logger.getLogger(LinstorStorageAdaptor.class);
+    private static final Map<String, KVMStoragePool> MapStorageUuidToStoragePool = new HashMap<>();
+
+    private DevelopersApi getLinstorAPI(KVMStoragePool pool) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(pool.getSourceHost());
+        return new DevelopersApi(client);
+    }
+
+    private String getLinstorRscName(String name) {
+        return "cs-" + name;
+    }
+
+    private String getHostname() {
+        // either there is already some function for that in the agent or a better way.
+        ProcessBuilder pb = new ProcessBuilder("hostname");
+        try
+        {
+            String result;
+            Process p = pb.start();
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+            StringJoiner sj = new StringJoiner(System.getProperty("line.separator"));
+            reader.lines().iterator().forEachRemaining(sj::add);
+            result = sj.toString();
+
+            p.waitFor();
+            p.destroy();
+            return result;
+        } catch (IOException | InterruptedException exc) {
+            throw new CloudRuntimeException("Unable to run hostname command.");
+        }
+    }
+
+    private void handleLinstorApiAnswers(ApiCallRcList answers, String excMessage) {
+        if (ApiCallChecker.hasError(answers)) {
+            for (ApiCallRc answer : answers) {
+                s_logger.error(answer.getMessage());
+            }
+            throw new CloudRuntimeException(excMessage);
+        }
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid, boolean refreshInfo) {
+        s_logger.debug("Linstor getStoragePool: " + uuid + " -> " + refreshInfo);
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMPhysicalDisk getPhysicalDisk(String name, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: getPhysicalDisk for " + name);
+        if (name == null) {
+            return null;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try {
+            final String rscName = getLinstorRscName(name);
+
+            List<VolumeDefinition> volumeDefs = api.volumeDefinitionList(rscName, null, null);
+            final long size = volumeDefs.isEmpty() ? 0 : volumeDefs.get(0).getSizeKib() * 1024;
+
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty()) {
+                final String devPath = resources.get(0).getVolumes().get(0).getDevicePath();
+                final KVMPhysicalDisk kvmDisk = new KVMPhysicalDisk(devPath, name, pool);
+                kvmDisk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
+                kvmDisk.setSize(size);
+                kvmDisk.setVirtualSize(size);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes for " + rscName);
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiExc) {
+            s_logger.error(apiExc);
+            throw new CloudRuntimeException(apiExc);
+        }
+    }
+
+    @Override
+    public KVMStoragePool createStoragePool(String name, String host, int port, String path, String userInfo,
+                                            Storage.StoragePoolType type)
+    {
+        s_logger.debug(String.format(
+            "Linstor createStoragePool: name: '%s', host: '%s', path: %s, userinfo: %s", name, host, path, userInfo));
+        LinstorStoragePool storagePool = new LinstorStoragePool(name, host, port, userInfo, type, this);
+
+        MapStorageUuidToStoragePool.put(name, storagePool);
+
+        return storagePool;
+    }
+
+    @Override
+    public boolean deleteStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.remove(uuid) != null;
+    }
+
+    @Override
+    public boolean deleteStoragePool(KVMStoragePool pool) {
+        return deleteStoragePool(pool.getUuid());
+    }
+
+    @Override
+    public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, QemuImg.PhysicalDiskFormat format,
+                                              Storage.ProvisioningType provisioningType, long size)
+    {
+        LinstorStoragePool lpool = (LinstorStoragePool) pool;
+        final DevelopersApi api = getLinstorAPI(pool);
+        ResourceGroupSpawn rgSpawn = new ResourceGroupSpawn();
+        final String rscName = getLinstorRscName(name);
+        rgSpawn.setResourceDefinitionName(rscName);
+        rgSpawn.addVolumeSizesItem(size / 1024); // linstor uses KiB
+        try {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = api.resourceGroupSpawn(lpool.getResourceGroup(), rgSpawn);
+            handleLinstorApiAnswers(answers, "Linstor: Unable to spawn resource.");
+
+            // query linstor for the device path
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            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);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiExc) {
+            throw new CloudRuntimeException(apiExc);
+        }
+    }
+
+    @Override
+    public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details)
+    {
+        s_logger.debug(String.format("Linstor: connectPhysicalDisk %s:%s -> %s", pool.getUuid(), volumePath, details));
+        if (volumePath == null) {
+            s_logger.warn("volumePath is null, ignoring");
+            return false;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try
+        {
+            final String rscName = getLinstorRscName(volumePath);
+            final String localNode = getHostname();
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.singletonList(localNode),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+
+            if (resources.isEmpty()) {
+                s_logger.info("Linstor: resource not created yet: " + rscName);
+                ResourceCreate rscCreate = new ResourceCreate();
+                Resource rsc = new Resource();
+                rsc.setNodeName(localNode);
+                rscCreate.setResource(rsc);
+                ApiCallRcList answers = api.resourceCreate(rscName, Collections.singletonList(rscCreate));
+                if (ApiCallChecker.hasError(answers)) {
+                    s_logger.error("Could not create resource " + rscName);
+                    throw new CloudRuntimeException(answers.get(0).getMessage());
+                }
+                // now diskful resource should be deployed
+            }
+            // keep diskless, maybe add an option, but I'm concerned about out of space situations
+//            else {
+//                final ResourceWithVolumes rsc = resources.get(0);
+//                if (rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS)) {
+//                    ApiCallRcList answers = api.resourceToggleDiskful(rscName, localNode);
+//                    if (ApiCallChecker.hasError(answers)) {
+//                        s_logger.error("Unable to toggle disk on " + rscName);
+//                        throw new CloudRuntimeException(answers.get(0).getMessage());
+//                    }
+//                }
+//            }
+
+            // allow 2 primaries for live migration, should be removed by disconnect on the other end
+            ResourceDefinitionModify rdm = new ResourceDefinitionModify();
+            Properties props = new Properties();
+            props.put("DrbdOptions/Net/allow-two-primaries", "yes");
+            rdm.setOverrideProps(props);
+            ApiCallRcList answers = api.resourceDefinitionModify(rscName, rdm);
+            if (ApiCallChecker.hasError(answers)) {
+                s_logger.error("Unable to set 'allow-two-primaries' on " + rscName);
+                throw new CloudRuntimeException(answers.get(0).getMessage());
+            }
+        } catch (ApiException apiExc) {
+            s_logger.error(apiExc);
+            throw new CloudRuntimeException(apiExc.getMessage());
+        }
+        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(String volumePath, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDisk " + pool.getUuid() + ":" + volumePath);
+        // TODO remove diskless/diskful on node
+        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDisk map");
+        // TODO remove diskless/diskful on node
+        return true;
+    }
+
+    private Optional<ResourceWithVolumes> getResourceByPath(final List<ResourceWithVolumes> resources, String path) {
+        return resources.stream()
+            .filter(rsc -> rsc.getVolumes().stream()
+                .anyMatch(v -> v.getDevicePath().equals(path)))
+            .findFirst();
+    }
+
+    /**
+     * disconnectPhysicalDiskByPath is called after e.g. a live migration.
+     * The problem is we have no idea just from the path to which linstor-controller
+     * this resource would belong to. But as it should be highly unlikely that someone
+     * uses more than one linstor-controller to manage resource on the same kvm host.
+     * We will just take the first stored storagepool.
+     */
+    @Override
+    public boolean disconnectPhysicalDiskByPath(String localPath)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDiskByPath " + localPath);
+        // get first storage pool from the map, as we don't now any better:
+        if (!MapStorageUuidToStoragePool.isEmpty())
+        {
+            String firstKey = MapStorageUuidToStoragePool.keySet().stream().findFirst().get();
+            final KVMStoragePool pool = MapStorageUuidToStoragePool.get(firstKey);
+
+            s_logger.debug("Linstor: Using storpool: " + pool.getUuid());
+            final DevelopersApi api = getLinstorAPI(pool);
+
+            try
+            {
+                List<ResourceWithVolumes> resources = api.viewResources(
+                    Collections.singletonList(getHostname()),
+                    null,
+                    null,
+                    null,
+                    null,
+                    null);
+
+                Optional<ResourceWithVolumes> rsc = getResourceByPath(resources, localPath);
+
+                if (rsc.isPresent())
+                {
+                    ResourceDefinitionModify rdm = new ResourceDefinitionModify();
+                    rdm.deleteProps(Collections.singletonList("DrbdOptions/Net/allow-two-primaries"));
+                    ApiCallRcList answers = api.resourceDefinitionModify(rsc.get().getName(), rdm);
+                    if (ApiCallChecker.hasError(answers))
+                    {
+                        s_logger.error("Failed to remove 'allow-two-primaries' on " + rsc.get().getName());
+                        throw new CloudRuntimeException(answers.get(0).getMessage());
+                    }
+
+                    return true;
+                }
+                s_logger.warn("Linstor: Couldn't find resource for this path: " + localPath);
+            } catch (ApiException apiExc) {
+                s_logger.error(apiExc);
+                throw new CloudRuntimeException(apiExc.getMessage());
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean deletePhysicalDisk(String name, KVMStoragePool pool, Storage.ImageFormat format)
+    {
+        s_logger.debug("Linstor: deletePhysicalDisk " + name);
+        final DevelopersApi api = getLinstorAPI(pool);
+
+        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);
+        } catch (ApiException apiExc) {
+            throw new CloudRuntimeException(apiExc);
+        }
+        return true;
+    }
+
+    @Override
+    public KVMPhysicalDisk createDiskFromTemplate(
+        KVMPhysicalDisk template,
+        String name,
+        QemuImg.PhysicalDiskFormat format,
+        Storage.ProvisioningType provisioningType,
+        long size,
+        KVMStoragePool destPool,
+        int timeout)
+    {
+        s_logger.info("Linstor: createDiskFromTemplate");
+        // if source is linstor lvm-thin snapshot we could snapshot and restore from, but do plain copy for now
+        return copyPhysicalDisk(template, name, destPool, timeout);
+    }
+
+    @Override
+    public List<KVMPhysicalDisk> listPhysicalDisks(String storagePoolUuid, KVMStoragePool pool)
+    {
+        throw new UnsupportedOperationException("Listing disks is not supported for this configuration.");
+    }
+
+    @Override
+    public KVMPhysicalDisk createTemplateFromDisk(
+        KVMPhysicalDisk disk,
+        String name,
+        QemuImg.PhysicalDiskFormat format,
+        long size,
+        KVMStoragePool destPool)
+    {
+        throw new UnsupportedOperationException("Copying a template from disk is not supported in this configuration.");
+    }
+
+    @Override
+    public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPools, int timeout)
+    {
+        s_logger.debug("Linstor: copyPhysicalDisk");
+        final QemuImg.PhysicalDiskFormat sourceFormat = disk.getFormat();
+        final String sourcePath = disk.getPath();
+        final QemuImg qemu = new QemuImg(timeout);
+
+        final QemuImgFile srcFile = new QemuImgFile(sourcePath, sourceFormat);
+
+        // create linstor resource

Review comment:
       The method name is clear and auto explanatory, I think we do not need this comment.

##########
File path: plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
##########
@@ -0,0 +1,566 @@
+package com.cloud.hypervisor.kvm.storage;
+
+import com.linbit.linstor.api.ApiCallChecker;
+import com.linbit.linstor.api.ApiClient;
+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;
+import com.linbit.linstor.api.model.Properties;
+import com.linbit.linstor.api.model.ProviderKind;
+import com.linbit.linstor.api.model.Resource;
+import com.linbit.linstor.api.model.ResourceCreate;
+import com.linbit.linstor.api.model.ResourceDefinitionModify;
+import com.linbit.linstor.api.model.ResourceGroup;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
+import com.linbit.linstor.api.model.StoragePool;
+import com.linbit.linstor.api.model.VolumeDefinition;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+@StorageAdaptorInfo(storagePoolType=Storage.StoragePoolType.Linstor)
+public class LinstorStorageAdaptor implements StorageAdaptor {
+    private static final Logger s_logger = Logger.getLogger(LinstorStorageAdaptor.class);
+    private static final Map<String, KVMStoragePool> MapStorageUuidToStoragePool = new HashMap<>();
+
+    private DevelopersApi getLinstorAPI(KVMStoragePool pool) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(pool.getSourceHost());
+        return new DevelopersApi(client);
+    }
+
+    private String getLinstorRscName(String name) {
+        return "cs-" + name;
+    }
+
+    private String getHostname() {
+        // either there is already some function for that in the agent or a better way.
+        ProcessBuilder pb = new ProcessBuilder("hostname");
+        try
+        {
+            String result;
+            Process p = pb.start();
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+            StringJoiner sj = new StringJoiner(System.getProperty("line.separator"));
+            reader.lines().iterator().forEachRemaining(sj::add);
+            result = sj.toString();
+
+            p.waitFor();
+            p.destroy();
+            return result;
+        } catch (IOException | InterruptedException exc) {
+            throw new CloudRuntimeException("Unable to run hostname command.");
+        }
+    }
+
+    private void handleLinstorApiAnswers(ApiCallRcList answers, String excMessage) {
+        if (ApiCallChecker.hasError(answers)) {
+            for (ApiCallRc answer : answers) {
+                s_logger.error(answer.getMessage());
+            }
+            throw new CloudRuntimeException(excMessage);
+        }
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid, boolean refreshInfo) {
+        s_logger.debug("Linstor getStoragePool: " + uuid + " -> " + refreshInfo);
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMPhysicalDisk getPhysicalDisk(String name, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: getPhysicalDisk for " + name);
+        if (name == null) {
+            return null;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try {
+            final String rscName = getLinstorRscName(name);
+
+            List<VolumeDefinition> volumeDefs = api.volumeDefinitionList(rscName, null, null);
+            final long size = volumeDefs.isEmpty() ? 0 : volumeDefs.get(0).getSizeKib() * 1024;
+
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty()) {
+                final String devPath = resources.get(0).getVolumes().get(0).getDevicePath();
+                final KVMPhysicalDisk kvmDisk = new KVMPhysicalDisk(devPath, name, pool);
+                kvmDisk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
+                kvmDisk.setSize(size);
+                kvmDisk.setVirtualSize(size);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes for " + rscName);
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiExc) {
+            s_logger.error(apiExc);
+            throw new CloudRuntimeException(apiExc);
+        }
+    }
+
+    @Override
+    public KVMStoragePool createStoragePool(String name, String host, int port, String path, String userInfo,
+                                            Storage.StoragePoolType type)
+    {
+        s_logger.debug(String.format(
+            "Linstor createStoragePool: name: '%s', host: '%s', path: %s, userinfo: %s", name, host, path, userInfo));
+        LinstorStoragePool storagePool = new LinstorStoragePool(name, host, port, userInfo, type, this);
+
+        MapStorageUuidToStoragePool.put(name, storagePool);
+
+        return storagePool;
+    }
+
+    @Override
+    public boolean deleteStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.remove(uuid) != null;
+    }
+
+    @Override
+    public boolean deleteStoragePool(KVMStoragePool pool) {
+        return deleteStoragePool(pool.getUuid());
+    }
+
+    @Override
+    public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, QemuImg.PhysicalDiskFormat format,
+                                              Storage.ProvisioningType provisioningType, long size)
+    {
+        LinstorStoragePool lpool = (LinstorStoragePool) pool;
+        final DevelopersApi api = getLinstorAPI(pool);
+        ResourceGroupSpawn rgSpawn = new ResourceGroupSpawn();
+        final String rscName = getLinstorRscName(name);
+        rgSpawn.setResourceDefinitionName(rscName);
+        rgSpawn.addVolumeSizesItem(size / 1024); // linstor uses KiB
+        try {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = api.resourceGroupSpawn(lpool.getResourceGroup(), rgSpawn);
+            handleLinstorApiAnswers(answers, "Linstor: Unable to spawn resource.");
+
+            // query linstor for the device path
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            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);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiExc) {
+            throw new CloudRuntimeException(apiExc);
+        }
+    }
+
+    @Override
+    public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details)
+    {
+        s_logger.debug(String.format("Linstor: connectPhysicalDisk %s:%s -> %s", pool.getUuid(), volumePath, details));
+        if (volumePath == null) {
+            s_logger.warn("volumePath is null, ignoring");
+            return false;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try
+        {
+            final String rscName = getLinstorRscName(volumePath);
+            final String localNode = getHostname();
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.singletonList(localNode),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+
+            if (resources.isEmpty()) {
+                s_logger.info("Linstor: resource not created yet: " + rscName);
+                ResourceCreate rscCreate = new ResourceCreate();
+                Resource rsc = new Resource();
+                rsc.setNodeName(localNode);
+                rscCreate.setResource(rsc);
+                ApiCallRcList answers = api.resourceCreate(rscName, Collections.singletonList(rscCreate));
+                if (ApiCallChecker.hasError(answers)) {
+                    s_logger.error("Could not create resource " + rscName);
+                    throw new CloudRuntimeException(answers.get(0).getMessage());
+                }
+                // now diskful resource should be deployed
+            }
+            // keep diskless, maybe add an option, but I'm concerned about out of space situations
+//            else {
+//                final ResourceWithVolumes rsc = resources.get(0);
+//                if (rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS)) {
+//                    ApiCallRcList answers = api.resourceToggleDiskful(rscName, localNode);
+//                    if (ApiCallChecker.hasError(answers)) {
+//                        s_logger.error("Unable to toggle disk on " + rscName);
+//                        throw new CloudRuntimeException(answers.get(0).getMessage());
+//                    }
+//                }
+//            }
+
+            // allow 2 primaries for live migration, should be removed by disconnect on the other end
+            ResourceDefinitionModify rdm = new ResourceDefinitionModify();
+            Properties props = new Properties();
+            props.put("DrbdOptions/Net/allow-two-primaries", "yes");
+            rdm.setOverrideProps(props);
+            ApiCallRcList answers = api.resourceDefinitionModify(rscName, rdm);
+            if (ApiCallChecker.hasError(answers)) {
+                s_logger.error("Unable to set 'allow-two-primaries' on " + rscName);
+                throw new CloudRuntimeException(answers.get(0).getMessage());
+            }
+        } catch (ApiException apiExc) {
+            s_logger.error(apiExc);
+            throw new CloudRuntimeException(apiExc.getMessage());
+        }
+        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(String volumePath, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDisk " + pool.getUuid() + ":" + volumePath);
+        // TODO remove diskless/diskful on node
+        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDisk map");
+        // TODO remove diskless/diskful on node
+        return true;
+    }
+
+    private Optional<ResourceWithVolumes> getResourceByPath(final List<ResourceWithVolumes> resources, String path) {
+        return resources.stream()
+            .filter(rsc -> rsc.getVolumes().stream()
+                .anyMatch(v -> v.getDevicePath().equals(path)))
+            .findFirst();
+    }
+
+    /**
+     * disconnectPhysicalDiskByPath is called after e.g. a live migration.
+     * The problem is we have no idea just from the path to which linstor-controller
+     * this resource would belong to. But as it should be highly unlikely that someone
+     * uses more than one linstor-controller to manage resource on the same kvm host.
+     * We will just take the first stored storagepool.
+     */
+    @Override
+    public boolean disconnectPhysicalDiskByPath(String localPath)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDiskByPath " + localPath);
+        // get first storage pool from the map, as we don't now any better:
+        if (!MapStorageUuidToStoragePool.isEmpty())
+        {
+            String firstKey = MapStorageUuidToStoragePool.keySet().stream().findFirst().get();
+            final KVMStoragePool pool = MapStorageUuidToStoragePool.get(firstKey);
+
+            s_logger.debug("Linstor: Using storpool: " + pool.getUuid());
+            final DevelopersApi api = getLinstorAPI(pool);
+
+            try
+            {
+                List<ResourceWithVolumes> resources = api.viewResources(
+                    Collections.singletonList(getHostname()),
+                    null,
+                    null,
+                    null,
+                    null,
+                    null);
+
+                Optional<ResourceWithVolumes> rsc = getResourceByPath(resources, localPath);
+
+                if (rsc.isPresent())
+                {
+                    ResourceDefinitionModify rdm = new ResourceDefinitionModify();
+                    rdm.deleteProps(Collections.singletonList("DrbdOptions/Net/allow-two-primaries"));
+                    ApiCallRcList answers = api.resourceDefinitionModify(rsc.get().getName(), rdm);
+                    if (ApiCallChecker.hasError(answers))
+                    {
+                        s_logger.error("Failed to remove 'allow-two-primaries' on " + rsc.get().getName());
+                        throw new CloudRuntimeException(answers.get(0).getMessage());
+                    }
+
+                    return true;
+                }
+                s_logger.warn("Linstor: Couldn't find resource for this path: " + localPath);
+            } catch (ApiException apiExc) {
+                s_logger.error(apiExc);
+                throw new CloudRuntimeException(apiExc.getMessage());
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean deletePhysicalDisk(String name, KVMStoragePool pool, Storage.ImageFormat format)
+    {
+        s_logger.debug("Linstor: deletePhysicalDisk " + name);
+        final DevelopersApi api = getLinstorAPI(pool);
+
+        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);
+        } catch (ApiException apiExc) {
+            throw new CloudRuntimeException(apiExc);
+        }
+        return true;
+    }
+
+    @Override
+    public KVMPhysicalDisk createDiskFromTemplate(
+        KVMPhysicalDisk template,
+        String name,
+        QemuImg.PhysicalDiskFormat format,
+        Storage.ProvisioningType provisioningType,
+        long size,
+        KVMStoragePool destPool,
+        int timeout)
+    {
+        s_logger.info("Linstor: createDiskFromTemplate");
+        // if source is linstor lvm-thin snapshot we could snapshot and restore from, but do plain copy for now
+        return copyPhysicalDisk(template, name, destPool, timeout);
+    }
+
+    @Override
+    public List<KVMPhysicalDisk> listPhysicalDisks(String storagePoolUuid, KVMStoragePool pool)
+    {
+        throw new UnsupportedOperationException("Listing disks is not supported for this configuration.");
+    }
+
+    @Override
+    public KVMPhysicalDisk createTemplateFromDisk(
+        KVMPhysicalDisk disk,
+        String name,
+        QemuImg.PhysicalDiskFormat format,
+        long size,
+        KVMStoragePool destPool)
+    {
+        throw new UnsupportedOperationException("Copying a template from disk is not supported in this configuration.");
+    }
+
+    @Override
+    public KVMPhysicalDisk copyPhysicalDisk(KVMPhysicalDisk disk, String name, KVMStoragePool destPools, int timeout)
+    {
+        s_logger.debug("Linstor: copyPhysicalDisk");
+        final QemuImg.PhysicalDiskFormat sourceFormat = disk.getFormat();
+        final String sourcePath = disk.getPath();
+        final QemuImg qemu = new QemuImg(timeout);
+
+        final QemuImgFile srcFile = new QemuImgFile(sourcePath, sourceFormat);
+
+        // create linstor resource
+        final KVMPhysicalDisk dstDisk = destPools.createPhysicalDisk(
+            name, QemuImg.PhysicalDiskFormat.RAW, Storage.ProvisioningType.FAT, disk.getVirtualSize());
+
+        final QemuImgFile destFile = new QemuImgFile(dstDisk.getPath());
+        destFile.setFormat(dstDisk.getFormat());
+        destFile.setSize(disk.getVirtualSize());
+
+        try {
+            qemu.convert(srcFile, destFile);
+        } catch (QemuImgException | LibvirtException e) {
+            s_logger.error("Failed to copy " + srcFile.getFileName() + " to " +
+                destFile.getFileName() + " the error was: " + e.getMessage());

Review comment:
       You can pass the exception as parameter here.

##########
File path: plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
##########
@@ -0,0 +1,566 @@
+package com.cloud.hypervisor.kvm.storage;
+
+import com.linbit.linstor.api.ApiCallChecker;
+import com.linbit.linstor.api.ApiClient;
+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;
+import com.linbit.linstor.api.model.Properties;
+import com.linbit.linstor.api.model.ProviderKind;
+import com.linbit.linstor.api.model.Resource;
+import com.linbit.linstor.api.model.ResourceCreate;
+import com.linbit.linstor.api.model.ResourceDefinitionModify;
+import com.linbit.linstor.api.model.ResourceGroup;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
+import com.linbit.linstor.api.model.StoragePool;
+import com.linbit.linstor.api.model.VolumeDefinition;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+@StorageAdaptorInfo(storagePoolType=Storage.StoragePoolType.Linstor)
+public class LinstorStorageAdaptor implements StorageAdaptor {
+    private static final Logger s_logger = Logger.getLogger(LinstorStorageAdaptor.class);
+    private static final Map<String, KVMStoragePool> MapStorageUuidToStoragePool = new HashMap<>();
+
+    private DevelopersApi getLinstorAPI(KVMStoragePool pool) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(pool.getSourceHost());
+        return new DevelopersApi(client);
+    }
+
+    private String getLinstorRscName(String name) {
+        return "cs-" + name;
+    }
+
+    private String getHostname() {
+        // either there is already some function for that in the agent or a better way.
+        ProcessBuilder pb = new ProcessBuilder("hostname");
+        try
+        {
+            String result;
+            Process p = pb.start();
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+            StringJoiner sj = new StringJoiner(System.getProperty("line.separator"));
+            reader.lines().iterator().forEachRemaining(sj::add);
+            result = sj.toString();
+
+            p.waitFor();
+            p.destroy();
+            return result;
+        } catch (IOException | InterruptedException exc) {
+            throw new CloudRuntimeException("Unable to run hostname command.");
+        }
+    }
+
+    private void handleLinstorApiAnswers(ApiCallRcList answers, String excMessage) {
+        if (ApiCallChecker.hasError(answers)) {
+            for (ApiCallRc answer : answers) {
+                s_logger.error(answer.getMessage());
+            }
+            throw new CloudRuntimeException(excMessage);
+        }
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid, boolean refreshInfo) {
+        s_logger.debug("Linstor getStoragePool: " + uuid + " -> " + refreshInfo);
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMPhysicalDisk getPhysicalDisk(String name, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: getPhysicalDisk for " + name);
+        if (name == null) {
+            return null;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try {
+            final String rscName = getLinstorRscName(name);
+
+            List<VolumeDefinition> volumeDefs = api.volumeDefinitionList(rscName, null, null);
+            final long size = volumeDefs.isEmpty() ? 0 : volumeDefs.get(0).getSizeKib() * 1024;
+
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty()) {
+                final String devPath = resources.get(0).getVolumes().get(0).getDevicePath();
+                final KVMPhysicalDisk kvmDisk = new KVMPhysicalDisk(devPath, name, pool);
+                kvmDisk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
+                kvmDisk.setSize(size);
+                kvmDisk.setVirtualSize(size);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes for " + rscName);
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiExc) {
+            s_logger.error(apiExc);
+            throw new CloudRuntimeException(apiExc);
+        }
+    }
+
+    @Override
+    public KVMStoragePool createStoragePool(String name, String host, int port, String path, String userInfo,
+                                            Storage.StoragePoolType type)
+    {
+        s_logger.debug(String.format(
+            "Linstor createStoragePool: name: '%s', host: '%s', path: %s, userinfo: %s", name, host, path, userInfo));
+        LinstorStoragePool storagePool = new LinstorStoragePool(name, host, port, userInfo, type, this);
+
+        MapStorageUuidToStoragePool.put(name, storagePool);
+
+        return storagePool;
+    }
+
+    @Override
+    public boolean deleteStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.remove(uuid) != null;
+    }
+
+    @Override
+    public boolean deleteStoragePool(KVMStoragePool pool) {
+        return deleteStoragePool(pool.getUuid());
+    }
+
+    @Override
+    public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, QemuImg.PhysicalDiskFormat format,
+                                              Storage.ProvisioningType provisioningType, long size)
+    {
+        LinstorStoragePool lpool = (LinstorStoragePool) pool;
+        final DevelopersApi api = getLinstorAPI(pool);
+        ResourceGroupSpawn rgSpawn = new ResourceGroupSpawn();
+        final String rscName = getLinstorRscName(name);
+        rgSpawn.setResourceDefinitionName(rscName);
+        rgSpawn.addVolumeSizesItem(size / 1024); // linstor uses KiB
+        try {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = api.resourceGroupSpawn(lpool.getResourceGroup(), rgSpawn);
+            handleLinstorApiAnswers(answers, "Linstor: Unable to spawn resource.");
+
+            // query linstor for the device path
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            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);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiExc) {
+            throw new CloudRuntimeException(apiExc);
+        }
+    }
+
+    @Override
+    public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details)
+    {
+        s_logger.debug(String.format("Linstor: connectPhysicalDisk %s:%s -> %s", pool.getUuid(), volumePath, details));
+        if (volumePath == null) {
+            s_logger.warn("volumePath is null, ignoring");
+            return false;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try
+        {
+            final String rscName = getLinstorRscName(volumePath);
+            final String localNode = getHostname();
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.singletonList(localNode),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+
+            if (resources.isEmpty()) {
+                s_logger.info("Linstor: resource not created yet: " + rscName);
+                ResourceCreate rscCreate = new ResourceCreate();
+                Resource rsc = new Resource();
+                rsc.setNodeName(localNode);
+                rscCreate.setResource(rsc);
+                ApiCallRcList answers = api.resourceCreate(rscName, Collections.singletonList(rscCreate));
+                if (ApiCallChecker.hasError(answers)) {
+                    s_logger.error("Could not create resource " + rscName);
+                    throw new CloudRuntimeException(answers.get(0).getMessage());
+                }
+                // now diskful resource should be deployed
+            }
+            // keep diskless, maybe add an option, but I'm concerned about out of space situations
+//            else {
+//                final ResourceWithVolumes rsc = resources.get(0);
+//                if (rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS)) {
+//                    ApiCallRcList answers = api.resourceToggleDiskful(rscName, localNode);
+//                    if (ApiCallChecker.hasError(answers)) {
+//                        s_logger.error("Unable to toggle disk on " + rscName);
+//                        throw new CloudRuntimeException(answers.get(0).getMessage());
+//                    }
+//                }
+//            }

Review comment:
       Why commit a commented code?

##########
File path: plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
##########
@@ -0,0 +1,566 @@
+package com.cloud.hypervisor.kvm.storage;
+
+import com.linbit.linstor.api.ApiCallChecker;
+import com.linbit.linstor.api.ApiClient;
+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;
+import com.linbit.linstor.api.model.Properties;
+import com.linbit.linstor.api.model.ProviderKind;
+import com.linbit.linstor.api.model.Resource;
+import com.linbit.linstor.api.model.ResourceCreate;
+import com.linbit.linstor.api.model.ResourceDefinitionModify;
+import com.linbit.linstor.api.model.ResourceGroup;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
+import com.linbit.linstor.api.model.StoragePool;
+import com.linbit.linstor.api.model.VolumeDefinition;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+@StorageAdaptorInfo(storagePoolType=Storage.StoragePoolType.Linstor)
+public class LinstorStorageAdaptor implements StorageAdaptor {
+    private static final Logger s_logger = Logger.getLogger(LinstorStorageAdaptor.class);
+    private static final Map<String, KVMStoragePool> MapStorageUuidToStoragePool = new HashMap<>();
+
+    private DevelopersApi getLinstorAPI(KVMStoragePool pool) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(pool.getSourceHost());
+        return new DevelopersApi(client);
+    }
+
+    private String getLinstorRscName(String name) {
+        return "cs-" + name;
+    }
+
+    private String getHostname() {
+        // either there is already some function for that in the agent or a better way.
+        ProcessBuilder pb = new ProcessBuilder("hostname");
+        try
+        {
+            String result;
+            Process p = pb.start();
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+            StringJoiner sj = new StringJoiner(System.getProperty("line.separator"));
+            reader.lines().iterator().forEachRemaining(sj::add);
+            result = sj.toString();
+
+            p.waitFor();
+            p.destroy();
+            return result;
+        } catch (IOException | InterruptedException exc) {
+            throw new CloudRuntimeException("Unable to run hostname command.");
+        }
+    }
+
+    private void handleLinstorApiAnswers(ApiCallRcList answers, String excMessage) {
+        if (ApiCallChecker.hasError(answers)) {
+            for (ApiCallRc answer : answers) {
+                s_logger.error(answer.getMessage());
+            }
+            throw new CloudRuntimeException(excMessage);
+        }
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid, boolean refreshInfo) {
+        s_logger.debug("Linstor getStoragePool: " + uuid + " -> " + refreshInfo);
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMPhysicalDisk getPhysicalDisk(String name, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: getPhysicalDisk for " + name);
+        if (name == null) {
+            return null;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try {
+            final String rscName = getLinstorRscName(name);
+
+            List<VolumeDefinition> volumeDefs = api.volumeDefinitionList(rscName, null, null);
+            final long size = volumeDefs.isEmpty() ? 0 : volumeDefs.get(0).getSizeKib() * 1024;
+
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty()) {
+                final String devPath = resources.get(0).getVolumes().get(0).getDevicePath();
+                final KVMPhysicalDisk kvmDisk = new KVMPhysicalDisk(devPath, name, pool);
+                kvmDisk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
+                kvmDisk.setSize(size);
+                kvmDisk.setVirtualSize(size);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes for " + rscName);
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiExc) {
+            s_logger.error(apiExc);
+            throw new CloudRuntimeException(apiExc);
+        }
+    }
+
+    @Override
+    public KVMStoragePool createStoragePool(String name, String host, int port, String path, String userInfo,
+                                            Storage.StoragePoolType type)
+    {
+        s_logger.debug(String.format(
+            "Linstor createStoragePool: name: '%s', host: '%s', path: %s, userinfo: %s", name, host, path, userInfo));
+        LinstorStoragePool storagePool = new LinstorStoragePool(name, host, port, userInfo, type, this);
+
+        MapStorageUuidToStoragePool.put(name, storagePool);
+
+        return storagePool;
+    }
+
+    @Override
+    public boolean deleteStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.remove(uuid) != null;
+    }
+
+    @Override
+    public boolean deleteStoragePool(KVMStoragePool pool) {
+        return deleteStoragePool(pool.getUuid());
+    }
+
+    @Override
+    public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, QemuImg.PhysicalDiskFormat format,
+                                              Storage.ProvisioningType provisioningType, long size)
+    {
+        LinstorStoragePool lpool = (LinstorStoragePool) pool;
+        final DevelopersApi api = getLinstorAPI(pool);
+        ResourceGroupSpawn rgSpawn = new ResourceGroupSpawn();
+        final String rscName = getLinstorRscName(name);
+        rgSpawn.setResourceDefinitionName(rscName);
+        rgSpawn.addVolumeSizesItem(size / 1024); // linstor uses KiB
+        try {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = api.resourceGroupSpawn(lpool.getResourceGroup(), rgSpawn);
+            handleLinstorApiAnswers(answers, "Linstor: Unable to spawn resource.");
+
+            // query linstor for the device path
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            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);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiExc) {
+            throw new CloudRuntimeException(apiExc);
+        }
+    }
+
+    @Override
+    public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details)
+    {
+        s_logger.debug(String.format("Linstor: connectPhysicalDisk %s:%s -> %s", pool.getUuid(), volumePath, details));
+        if (volumePath == null) {
+            s_logger.warn("volumePath is null, ignoring");
+            return false;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try
+        {
+            final String rscName = getLinstorRscName(volumePath);
+            final String localNode = getHostname();
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.singletonList(localNode),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+
+            if (resources.isEmpty()) {
+                s_logger.info("Linstor: resource not created yet: " + rscName);
+                ResourceCreate rscCreate = new ResourceCreate();
+                Resource rsc = new Resource();
+                rsc.setNodeName(localNode);
+                rscCreate.setResource(rsc);
+                ApiCallRcList answers = api.resourceCreate(rscName, Collections.singletonList(rscCreate));
+                if (ApiCallChecker.hasError(answers)) {
+                    s_logger.error("Could not create resource " + rscName);
+                    throw new CloudRuntimeException(answers.get(0).getMessage());
+                }
+                // now diskful resource should be deployed
+            }
+            // keep diskless, maybe add an option, but I'm concerned about out of space situations
+//            else {
+//                final ResourceWithVolumes rsc = resources.get(0);
+//                if (rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS)) {
+//                    ApiCallRcList answers = api.resourceToggleDiskful(rscName, localNode);
+//                    if (ApiCallChecker.hasError(answers)) {
+//                        s_logger.error("Unable to toggle disk on " + rscName);
+//                        throw new CloudRuntimeException(answers.get(0).getMessage());
+//                    }
+//                }
+//            }
+
+            // allow 2 primaries for live migration, should be removed by disconnect on the other end
+            ResourceDefinitionModify rdm = new ResourceDefinitionModify();
+            Properties props = new Properties();
+            props.put("DrbdOptions/Net/allow-two-primaries", "yes");
+            rdm.setOverrideProps(props);
+            ApiCallRcList answers = api.resourceDefinitionModify(rscName, rdm);
+            if (ApiCallChecker.hasError(answers)) {
+                s_logger.error("Unable to set 'allow-two-primaries' on " + rscName);
+                throw new CloudRuntimeException(answers.get(0).getMessage());
+            }
+        } catch (ApiException apiExc) {
+            s_logger.error(apiExc);
+            throw new CloudRuntimeException(apiExc.getMessage());
+        }
+        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(String volumePath, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDisk " + pool.getUuid() + ":" + volumePath);
+        // TODO remove diskless/diskful on node
+        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDisk map");
+        // TODO remove diskless/diskful on node

Review comment:
       Would be better to register TODO in issues instead of comments in the code.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-919125926


   Packaging result: :heavy_check_mark: el7 :heavy_check_mark: el8 :heavy_check_mark: debian :heavy_check_mark: suse15. SL-JID 1242


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] nvazquez commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
nvazquez commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-914342878


   @blueorangutan package


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r702830681



##########
File path: plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
##########
@@ -0,0 +1,720 @@
+// 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.driver;
+
+import com.linbit.linstor.api.ApiException;
+import com.linbit.linstor.api.CloneWaiter;
+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.ResourceDefinition;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted;
+import com.linbit.linstor.api.model.ResourceDefinitionCreate;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+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.VolumeDefinitionModify;
+
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.agent.api.to.DiskTO;
+import com.cloud.host.Host;
+import com.cloud.storage.ResizeVolumePayload;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.VolumeDetailVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.SnapshotDetailsDao;
+import com.cloud.storage.dao.SnapshotDetailsVO;
+import com.cloud.storage.dao.VMTemplatePoolDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.storage.command.CreateObjectAnswer;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
+import org.apache.cloudstack.storage.volume.VolumeObject;
+import org.apache.log4j.Logger;
+
+public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver {
+    private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class);
+    @Inject private PrimaryDataStoreDao _storagePoolDao;
+    @Inject private VolumeDao _volumeDao;
+    @Inject private VolumeDetailsDao _volumeDetailsDao;
+    @Inject private VMTemplatePoolDao _vmTemplatePoolDao;
+    @Inject private SnapshotDao _snapshotDao;
+    @Inject private SnapshotDetailsDao _snapshotDetailsDao;
+
+    public LinstorPrimaryDataStoreDriverImpl()
+    {
+    }
+
+    @Override
+    public Map<String, String> getCapabilities()
+    {
+        Map<String, String> mapCapabilities = new HashMap<>();
+
+        // Linstor will be restricted to only run on LVM-THIN and ZFS storage pools with ACS
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString(), Boolean.TRUE.toString());
+
+        // fetch if lvm-thin or ZFS
+        mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        // CAN_CREATE_VOLUME_FROM_SNAPSHOT see note from CAN_CREATE_VOLUME_FROM_VOLUME
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+        mapCapabilities.put(DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        return mapCapabilities;
+    }
+
+    @Override
+    public DataTO getTO(DataObject data)
+    {
+        return null;
+    }
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store)
+    {
+        return null;
+    }
+
+    @Override
+    public ChapInfo getChapInfo(DataObject dataObject)
+    {
+        return null;
+    }
+
+    @Override
+    public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+        return false;
+    }
+
+    @Override
+    public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+    }
+
+    @Override
+    public long getUsedBytes(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getUsedIops(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool)
+    {
+        return dataObject.getSize();
+    }
+
+    @Override
+    public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    private String getSnapshotName(String snapshotUuid) {
+        return LinstorUtil.RSC_PREFIX + snapshotUuid;
+    }
+
+    private void deleteResourceDefinition(StoragePoolVO storagePoolVO, String rscDefName)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceDefinitionDelete(rscDefName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete resource definition: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private void deleteSnapshot(@Nonnull DataStore dataStore, @Nonnull String rscDefName, @Nonnull String snapshotName)
+    {
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceSnapshotDelete(rscDefName, snapshotName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete snapshot: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private long getCsIdForCloning(long volumeId, String cloneOf) {
+        VolumeDetailVO volumeDetail = _volumeDetailsDao.findDetail(volumeId, cloneOf);
+
+        if (volumeDetail != null && volumeDetail.getValue() != null) {
+            return Long.parseLong(volumeDetail.getValue());
+        }
+
+        return Long.MIN_VALUE;
+    }
+
+    @Override
+    public void deleteAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("deleteAsync: " + dataObject.getType() + ";" + dataObject.getUuid());
+        String errMsg = null;
+
+        final long storagePoolId = dataStore.getId();
+        final StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
+
+        switch (dataObject.getType()) {
+            case VOLUME:
+            {
+                final VolumeInfo volumeInfo = (VolumeInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getPath();
+                deleteResourceDefinition(storagePool, rscName);
+
+                long usedBytes = storagePool.getUsedBytes();
+                long capacityIops = storagePool.getCapacityIops();
+
+                usedBytes -= volumeInfo.getSize();
+                if (volumeInfo.getMaxIops() != null)
+                    capacityIops += volumeInfo.getMaxIops();
+
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                storagePool.setCapacityIops(Math.max(0, capacityIops));
+
+                _storagePoolDao.update(storagePoolId, storagePool);
+            }
+                break;
+            case SNAPSHOT:
+                final SnapshotInfo snapshotInfo = (SnapshotInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + snapshotInfo.getBaseVolume().getPath();
+                deleteSnapshot(dataStore, rscName, getSnapshotName(snapshotInfo.getUuid()));
+                long usedBytes = storagePool.getUsedBytes() - snapshotInfo.getSize();
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                _storagePoolDao.update(storagePoolId, storagePool);
+                break;
+            default:
+                errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync";
+                s_logger.error(errMsg);
+        }
+
+        if (callback != null) {
+            CommandResult result = new CommandResult();
+            result.setResult(errMsg);
+
+            callback.complete(result);
+        }
+    }
+
+    private void logLinstorAnswer(@Nonnull ApiCallRc answer) {
+        if (answer.isError()) {
+            s_logger.error(answer.getMessage());
+        } else if (answer.isWarning()) {
+            s_logger.warn(answer.getMessage());
+        } else if (answer.isInfo()) {
+            s_logger.info(answer.getMessage());
+        }
+    }
+
+    private void logLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        answers.forEach(this::logLinstorAnswer);
+    }
+
+    private void checkLinstorAnswersThrow(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        if (answers.hasError())
+        {
+            String errMsg = answers.stream()
+                .filter(ApiCallRc::isError)
+                .findFirst()
+                .map(ApiCallRc::getMessage).orElse("Unknown linstor error");
+            throw new CloudRuntimeException(errMsg);
+        }
+    }
+
+    private String checkLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        return answers.stream().filter(ApiCallRc::isError).findFirst().map(ApiCallRc::getMessage).orElse(null);
+    }
+
+    private String getDeviceName(DevelopersApi linstorApi, String rscName) throws ApiException {
+        List<ResourceWithVolumes> resources = linstorApi.viewResources(
+            Collections.emptyList(),
+            Collections.singletonList(rscName),
+            Collections.emptyList(),
+            null,
+            null,
+            null);
+        if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty())
+        {
+            s_logger.info("Linstor: Created drbd device: " + resources.get(0).getVolumes().get(0).getDevicePath());
+            return resources.get(0).getVolumes().get(0).getDevicePath();
+        } else
+        {
+            s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+            throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+        }
+    }
+
+    private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+
+        ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn();
+        final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
+        rscGrpSpawn.setResourceDefinitionName(rscName);
+        rscGrpSpawn.addVolumeSizesItem(vol.getSize() / 1024);
+
+        try
+        {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = linstorApi.resourceGroupSpawn(rscGrp, rscGrpSpawn);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String cloneResource(long csCloneId, VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        // get the cached template on this storage
+        VMTemplateStoragePoolVO tmplPoolRef = _vmTemplatePoolDao.findByPoolTemplate(
+            storagePoolVO.getId(), csCloneId, null);
+
+        if (tmplPoolRef != null) {
+            final String cloneRes = LinstorUtil.RSC_PREFIX + tmplPoolRef.getLocalDownloadPath();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+            try {
+                s_logger.debug("Clone resource definition " + cloneRes + " to " + rscName);
+                ResourceDefinitionCloneRequest cloneRequest = new ResourceDefinitionCloneRequest();
+                cloneRequest.setName(rscName);
+                ResourceDefinitionCloneStarted cloneStarted = linstorApi.resourceDefinitionClone(
+                    cloneRes, cloneRequest);
+
+                checkLinstorAnswersThrow(cloneStarted.getMessages());
+
+                if (!CloneWaiter.waitFor(linstorApi, cloneStarted)) {
+                    throw new CloudRuntimeException("Clone for resource " + rscName + " failed.");
+                }
+
+                return getDeviceName(linstorApi, rscName);
+            } catch (ApiException apiEx) {
+                s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+                throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+            }
+        } else {
+            throw new CloudRuntimeException(
+                "Unable to find Linstor resource for the following template data-object ID: " + csCloneId);
+        }
+    }
+
+    private String createResourceFromSnapshot(long csSnapshotId, String rscName, StoragePoolVO storagePoolVO) {
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+        final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        SnapshotVO snapshotVO = _snapshotDao.findById(csSnapshotId);
+        String snapName = LinstorUtil.RSC_PREFIX + snapshotVO.getUuid();
+        VolumeVO volumeVO = _volumeDao.findById(snapshotVO.getVolumeId());
+        String cloneRes = LinstorUtil.RSC_PREFIX + volumeVO.getPath();
+
+        try
+        {
+            s_logger.debug("Create new resource definition: " + rscName);
+            ResourceDefinitionCreate rdCreate = new ResourceDefinitionCreate();
+            ResourceDefinition rd = new ResourceDefinition();
+            rd.setName(rscName);
+            rd.setResourceGroupName(rscGrp);
+            rdCreate.setResourceDefinition(rd);
+            ApiCallRcList answers = linstorApi.resourceDefinitionCreate(rdCreate);
+            checkLinstorAnswersThrow(answers);
+
+            SnapshotRestore snapshotRestore = new SnapshotRestore();
+            snapshotRestore.toResource(rscName);
+
+            s_logger.debug("Create new volume definition for snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotsRestoreVolumeDefinition(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            // restore snapshot to new resource
+            s_logger.debug("Restore resource from snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotRestore(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String createVolume(VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = getCsIdForCloning(volumeInfo.getId(), "cloneOfSnapshot");
+        long csTemplateId = getCsIdForCloning(volumeInfo.getId(), "cloneOfTemplate");
+
+        if (csSnapshotId > 0) {
+            return createResourceFromSnapshot(csSnapshotId, LinstorUtil.RSC_PREFIX + volumeInfo.getUuid(), storagePoolVO);
+        } else if (csTemplateId > 0) {
+            return cloneResource(csTemplateId, volumeInfo, storagePoolVO);
+        } else {
+            return createResource(volumeInfo, storagePoolVO);
+        }
+    }
+
+    private void handleSnapshotDetails(long csSnapshotId, String name, String value) {
+        _snapshotDetailsDao.removeDetail(csSnapshotId, name);
+        SnapshotDetailsVO snapshotDetails = new SnapshotDetailsVO(csSnapshotId, name, value, false);
+        _snapshotDetailsDao.persist(snapshotDetails);
+    }
+
+    private void addTempVolumeToDb(long csSnapshotId, String tempVolumeName) {
+        // TEMP_VOLUME_ID is needed, to find which temporary resource should be deleted after copying it on agent side
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, LinstorUtil.RSC_PREFIX + tempVolumeName);
+        // the iqn will be used on the agent side to copy from, even though linstor doesn't have anything to do with IQN
+        handleSnapshotDetails(csSnapshotId, DiskTO.IQN, tempVolumeName);
+    }
+
+    private void removeTempVolumeFromDb(long csSnapshotId) {
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+        if (snapshotDetails == null || snapshotDetails.getValue() == null) {
+            throw new CloudRuntimeException(
+                "'removeTempVolumeId' should not be invoked unless " + LinstorUtil.TEMP_VOLUME_ID + " exists.");
+        }
+
+        String originalVolumeId = snapshotDetails.getValue();
+
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, originalVolumeId);
+
+        _snapshotDetailsDao.remove(snapshotDetails.getId());
+    }
+
+    private void createVolumeFromSnapshot(SnapshotInfo snapshotInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = snapshotInfo.getId();
+
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, "tempVolume");
+
+        if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("create"))
+        {
+            final String csName = "Temp-" + snapshotInfo.getUuid();
+            final String tempRscName = LinstorUtil.RSC_PREFIX + csName;
+            createResourceFromSnapshot(csSnapshotId, tempRscName, storagePoolVO);
+
+            s_logger.debug("Temp resource created: " + tempRscName);
+            addTempVolumeToDb(csSnapshotId, csName);
+        }
+        else if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("delete"))
+        {
+            snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+            deleteResourceDefinition(storagePoolVO, snapshotDetails.getValue());
+
+            s_logger.debug("Temp resource deleted: " + snapshotDetails.getValue());
+            removeTempVolumeFromDb(csSnapshotId);
+        }
+        else {
+            throw new CloudRuntimeException("Invalid state in 'createVolumeFromSnapshot(SnapshotInfo, StoragePoolVO)'");
+        }
+    }
+
+    @Override
+    public void createAsync(DataStore dataStore, DataObject vol, AsyncCompletionCallback<CreateCmdResult> callback)
+    {
+        String devPath = null;
+        String errMsg = null;
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+
+        try
+        {
+            switch (vol.getType())
+            {
+                case VOLUME:
+                    VolumeInfo volumeInfo = (VolumeInfo) vol;
+                    VolumeVO volume = _volumeDao.findById(volumeInfo.getId());
+                    s_logger.debug("createAsync - creating volume");
+                    devPath = createVolume(volumeInfo, storagePool);
+                    volume.setFolder("/dev/");
+                    volume.setPoolId(storagePool.getId());
+                    volume.setUuid(vol.getUuid());
+                    volume.setPath(vol.getUuid());
+
+                    _volumeDao.update(volume.getId(), volume);
+                    break;
+                case SNAPSHOT:
+                    s_logger.debug("createAsync - SNAPSHOT");
+                    createVolumeFromSnapshot((SnapshotInfo) vol, storagePool);
+                    break;
+                case TEMPLATE:
+                    errMsg = "creating template - not supported";
+                    s_logger.error("createAsync - " + errMsg);
+                    break;
+                default:
+                    errMsg = "Invalid DataObjectType (" + vol.getType() + ") passed to createAsync";
+                    s_logger.error(errMsg);
+            }
+        } catch (Exception ex)
+        {
+            errMsg = ex.getMessage();
+
+            s_logger.error("createAsync: " + errMsg);
+            if (callback == null)
+            {
+                throw ex;
+            }
+        }
+
+        if (callback != null)
+        {
+            CreateCmdResult result = new CreateCmdResult(devPath, new Answer(null, errMsg == null, errMsg));
+            result.setResult(errMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public void revertSnapshot(
+        SnapshotInfo snapshot,
+        SnapshotInfo snapshotOnPrimaryStore,
+        AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("Linstor: revertSnapshot");
+        final VolumeInfo volumeInfo = snapshot.getBaseVolume();
+        VolumeVO volumeVO = _volumeDao.findById(volumeInfo.getId());
+        if (volumeVO == null || volumeVO.getRemoved() != null) {
+            CommandResult commandResult = new CommandResult();
+            commandResult.setResult("The volume that the snapshot belongs to no longer exists.");
+            callback.complete(commandResult);
+            return;
+        }
+
+        String resultMsg;
+        try {
+            final StoragePool pool = (StoragePool) snapshot.getDataStore();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final String snapName = LinstorUtil.RSC_PREFIX + snapshot.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(pool.getHostAddress());
+
+            ApiCallRcList answers = linstorApi.resourceSnapshotRollback(rscName, snapName);
+            resultMsg = checkLinstorAnswers(answers);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            resultMsg = apiEx.getBestMessage();
+        }
+
+        if (callback != null)
+        {
+            CommandResult result = new CommandResult();
+            result.setResult(resultMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public boolean canCopy(DataObject srcData, DataObject destData)
+    {
+        return false;
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback)

Review comment:
       I tried resizing, indeed online resizing isn't noticed by the guest. But if i power down and start the guest again. it has the new size available. I looked into other storage drivers, but didn't really see were they would notify the virtualization platform of the size change?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909346818


   @rhtyd a Trillian-Jenkins test job (centos7 mgmt + kvm-centos7) has been kicked to run smoke tests


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] sureshanaparti commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
sureshanaparti commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r626278462



##########
File path: plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java
##########
@@ -0,0 +1,195 @@
+package com.cloud.hypervisor.kvm.storage;
+
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.storage.Storage;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+
+public class LinstorStoragePool implements KVMStoragePool {
+    private final String _uuid;
+    private final String _sourceHost;
+    private final int _sourcePort;
+    private final Storage.StoragePoolType _storagePoolType;
+    private final StorageAdaptor _storageAdaptor;
+    private final String _resourceGroup;
+
+    public LinstorStoragePool(
+            String uuid,
+            String host,
+            int port,
+            String resourceGroup,
+            Storage.StoragePoolType storagePoolType,
+            StorageAdaptor storageAdaptor) {
+        _uuid = uuid;
+        _sourceHost = host;
+        _sourcePort = port;
+        _storagePoolType = storagePoolType;
+        _storageAdaptor = storageAdaptor;
+        _resourceGroup = resourceGroup;
+    }
+
+    @Override
+    public KVMPhysicalDisk createPhysicalDisk(
+        String name,
+        QemuImg.PhysicalDiskFormat format,
+        Storage.ProvisioningType provisioningType,
+        long size)

Review comment:
       @rp- please ensure the code format is in-line with the current formatting, and is consistent.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] nvazquez commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
nvazquez commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-917073859


   @rp- there is a failure on a unit test, there may be other failures as well but were not executed. Please check if you are building using the `-DskipTests` flag, you may want to remove it in order to run the unit tests
   
   ````
   [INFO] -------------------------------------------------------
   [INFO]  T E S T S
   [INFO] -------------------------------------------------------
   [INFO] Running com.cloud.hypervisor.kvm.resource.LibvirtDomainXMLParserTest
   [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.237 s - in com.cloud.hypervisor.kvm.resource.LibvirtDomainXMLParserTest
   [INFO] Running com.cloud.hypervisor.kvm.resource.LibvirtComputingResourceTest
   log4j:WARN No appenders could be found for logger (com.cloud.agent.properties.AgentPropertiesFileHandler).
   log4j:WARN Please initialize the log4j system properly.
   log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
   [ERROR] Tests run: 187, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 6.037 s <<< FAILURE! - in com.cloud.hypervisor.kvm.resource.LibvirtComputingResourceTest
   [ERROR] testResizeVolumeCommand(com.cloud.hypervisor.kvm.resource.LibvirtComputingResourceTest)  Time elapsed: 0.027 s  <<< FAILURE!
   java.lang.AssertionError
   	at com.cloud.hypervisor.kvm.resource.LibvirtComputingResourceTest.testResizeVolumeCommand(LibvirtComputingResourceTest.java:4871)
   ````


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-880456267


   @blueorangutan test


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-878879909


   @rhtyd Pinging again, is there anything you still need from for this PR? Or what steps are missing to proceed?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] slavkap commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
slavkap commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r673977712



##########
File path: plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
##########
@@ -0,0 +1,578 @@
+package com.cloud.hypervisor.kvm.storage;
+
+import com.linbit.linstor.api.ApiClient;
+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;
+import com.linbit.linstor.api.model.Properties;
+import com.linbit.linstor.api.model.ProviderKind;
+import com.linbit.linstor.api.model.Resource;
+import com.linbit.linstor.api.model.ResourceCreate;
+import com.linbit.linstor.api.model.ResourceDefinition;
+import com.linbit.linstor.api.model.ResourceDefinitionModify;
+import com.linbit.linstor.api.model.ResourceGroup;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
+import com.linbit.linstor.api.model.StoragePool;
+import com.linbit.linstor.api.model.VolumeDefinition;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+@StorageAdaptorInfo(storagePoolType=Storage.StoragePoolType.Linstor)
+public class LinstorStorageAdaptor implements StorageAdaptor {
+    private static final Logger s_logger = Logger.getLogger(LinstorStorageAdaptor.class);
+    private static final Map<String, KVMStoragePool> MapStorageUuidToStoragePool = new HashMap<>();
+    private final String localNodeName;
+
+    private DevelopersApi getLinstorAPI(KVMStoragePool pool) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(pool.getSourceHost());
+        return new DevelopersApi(client);
+    }
+
+    private String getLinstorRscName(String name) {
+        return "cs-" + name;
+    }
+
+    private String getHostname() {
+        // either there is already some function for that in the agent or a better way.
+        ProcessBuilder pb = new ProcessBuilder("hostname");
+        try
+        {
+            String result;
+            Process p = pb.start();
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+            StringJoiner sj = new StringJoiner(System.getProperty("line.separator"));
+            reader.lines().iterator().forEachRemaining(sj::add);
+            result = sj.toString();
+
+            p.waitFor();
+            p.destroy();
+            return result.trim();
+        } catch (IOException | InterruptedException exc) {
+            throw new CloudRuntimeException("Unable to run 'hostname' command.");
+        }
+    }
+
+    private void handleLinstorApiAnswers(ApiCallRcList answers, String excMessage) {
+        if (answers.hasError()) {
+            for (ApiCallRc answer : answers) {
+                s_logger.error(answer.getMessage());
+            }
+            throw new CloudRuntimeException(excMessage);
+        }
+    }
+
+    public LinstorStorageAdaptor() {
+        localNodeName = getHostname();
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid, boolean refreshInfo) {
+        s_logger.debug("Linstor getStoragePool: " + uuid + " -> " + refreshInfo);
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMPhysicalDisk getPhysicalDisk(String name, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: getPhysicalDisk for " + name);
+        if (name == null) {
+            return null;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try {
+            final String rscName = getLinstorRscName(name);
+
+            List<VolumeDefinition> volumeDefs = api.volumeDefinitionList(rscName, null, null);
+            final long size = volumeDefs.isEmpty() ? 0 : volumeDefs.get(0).getSizeKib() * 1024;
+
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty()) {
+                final String devPath = resources.get(0).getVolumes().get(0).getDevicePath();
+                final KVMPhysicalDisk kvmDisk = new KVMPhysicalDisk(devPath, name, pool);
+                kvmDisk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
+                kvmDisk.setSize(size);
+                kvmDisk.setVirtualSize(size);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes for " + rscName);
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiEx) {
+            s_logger.error(apiEx);
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    @Override
+    public KVMStoragePool createStoragePool(String name, String host, int port, String path, String userInfo,
+                                            Storage.StoragePoolType type)
+    {
+        s_logger.debug(String.format(
+            "Linstor createStoragePool: name: '%s', host: '%s', path: %s, userinfo: %s", name, host, path, userInfo));
+        LinstorStoragePool storagePool = new LinstorStoragePool(name, host, port, userInfo, type, this);
+
+        MapStorageUuidToStoragePool.put(name, storagePool);
+
+        return storagePool;
+    }
+
+    @Override
+    public boolean deleteStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.remove(uuid) != null;
+    }
+
+    @Override
+    public boolean deleteStoragePool(KVMStoragePool pool) {
+        return deleteStoragePool(pool.getUuid());
+    }
+
+    @Override
+    public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, QemuImg.PhysicalDiskFormat format,
+                                              Storage.ProvisioningType provisioningType, long size)
+    {
+        final String rscName = getLinstorRscName(name);
+        LinstorStoragePool lpool = (LinstorStoragePool) pool;
+        final DevelopersApi api = getLinstorAPI(pool);
+
+        try {
+            List<ResourceDefinition> definitionList = api.resourceDefinitionList(
+                Collections.singletonList(rscName), null, null, null);
+
+            if (definitionList.isEmpty()) {
+                ResourceGroupSpawn rgSpawn = new ResourceGroupSpawn();
+                rgSpawn.setResourceDefinitionName(rscName);
+                rgSpawn.addVolumeSizesItem(size / 1024); // linstor uses KiB
+
+                s_logger.debug("Linstor: Spawn resource " + rscName);
+                ApiCallRcList answers = api.resourceGroupSpawn(lpool.getResourceGroup(), rgSpawn);
+                handleLinstorApiAnswers(answers, "Linstor: Unable to spawn resource.");
+            }
+
+            // query linstor for the device path
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+
+            // TODO make avialable on node
+
+            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);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiEx) {
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    @Override
+    public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details)
+    {
+        s_logger.debug(String.format("Linstor: connectPhysicalDisk %s:%s -> %s", pool.getUuid(), volumePath, details));
+        if (volumePath == null) {
+            s_logger.warn("volumePath is null, ignoring");
+            return false;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try
+        {
+            final String rscName = getLinstorRscName(volumePath);
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.singletonList(localNodeName),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+
+            if (resources.isEmpty()) {
+                s_logger.info("Linstor: resource not created yet: " + rscName);
+                ResourceCreate rscCreate = new ResourceCreate();
+                Resource rsc = new Resource();
+                rsc.setNodeName(localNodeName);
+                rscCreate.setResource(rsc);
+                ApiCallRcList answers = api.resourceCreate(rscName, Collections.singletonList(rscCreate));
+                if (answers.hasError()) {
+                    s_logger.error("Could not create resource " + rscName);
+                    throw new CloudRuntimeException(answers.get(0).getMessage());
+                }
+                // now diskful resource should be deployed
+            }
+            // keep diskless, maybe add an option, but I'm concerned about out of space situations
+//            else {
+//                final ResourceWithVolumes rsc = resources.get(0);
+//                if (rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS)) {
+//                    ApiCallRcList answers = api.resourceToggleDiskful(rscName, localNode);
+//                    if (ApiCallChecker.hasError(answers)) {
+//                        s_logger.error("Unable to toggle disk on " + rscName);
+//                        throw new CloudRuntimeException(answers.get(0).getMessage());
+//                    }
+//                }
+//            }
+
+            // allow 2 primaries for live migration, should be removed by disconnect on the other end
+            ResourceDefinitionModify rdm = new ResourceDefinitionModify();
+            Properties props = new Properties();
+            props.put("DrbdOptions/Net/allow-two-primaries", "yes");
+            rdm.setOverrideProps(props);
+            ApiCallRcList answers = api.resourceDefinitionModify(rscName, rdm);
+            if (answers.hasError()) {
+                s_logger.error("Unable to set 'allow-two-primaries' on " + rscName);
+                throw new CloudRuntimeException(answers.get(0).getMessage());
+            }
+        } catch (ApiException apiEx) {
+            s_logger.error(apiEx);
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(String volumePath, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDisk " + pool.getUuid() + ":" + volumePath);
+        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDisk map");
+        return true;
+    }
+
+    private Optional<ResourceWithVolumes> getResourceByPath(final List<ResourceWithVolumes> resources, String path) {
+        return resources.stream()
+            .filter(rsc -> rsc.getVolumes().stream()
+                .anyMatch(v -> v.getDevicePath().equals(path)))
+            .findFirst();
+    }
+
+    /**
+     * disconnectPhysicalDiskByPath is called after e.g. a live migration.
+     * The problem is we have no idea just from the path to which linstor-controller
+     * this resource would belong to. But as it should be highly unlikely that someone
+     * uses more than one linstor-controller to manage resource on the same kvm host.
+     * We will just take the first stored storagepool.
+     */
+    @Override
+    public boolean disconnectPhysicalDiskByPath(String localPath)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDiskByPath " + localPath);
+        // get first storage pool from the map, as we don't know any better:
+        if (!MapStorageUuidToStoragePool.isEmpty())
+        {
+            String firstKey = MapStorageUuidToStoragePool.keySet().stream().findFirst().get();
+            final KVMStoragePool pool = MapStorageUuidToStoragePool.get(firstKey);
+
+            s_logger.debug("Linstor: Using storpool: " + pool.getUuid());
+            final DevelopersApi api = getLinstorAPI(pool);
+
+            try
+            {
+                List<ResourceWithVolumes> resources = api.viewResources(
+                    Collections.singletonList(localNodeName),
+                    null,
+                    null,
+                    null,
+                    null,
+                    null);
+
+                Optional<ResourceWithVolumes> rsc = getResourceByPath(resources, localPath);
+
+                if (rsc.isPresent())
+                {
+                    ResourceDefinitionModify rdm = new ResourceDefinitionModify();
+                    rdm.deleteProps(Collections.singletonList("DrbdOptions/Net/allow-two-primaries"));
+                    ApiCallRcList answers = api.resourceDefinitionModify(rsc.get().getName(), rdm);
+                    if (answers.hasError())
+                    {
+                        s_logger.error("Failed to remove 'allow-two-primaries' on " + rsc.get().getName());
+                        throw new CloudRuntimeException(answers.get(0).getMessage());
+                    }
+
+                    return true;
+                }
+                s_logger.warn("Linstor: Couldn't find resource for this path: " + localPath);
+            } catch (ApiException apiEx) {
+                s_logger.error(apiEx);
+                throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean deletePhysicalDisk(String name, KVMStoragePool pool, Storage.ImageFormat format)
+    {
+        s_logger.debug("Linstor: deletePhysicalDisk " + name);
+        final DevelopersApi api = getLinstorAPI(pool);
+
+        try {
+            final String rscName = getLinstorRscName(name);
+            s_logger.debug("Linstor: delete resource definition " + rscName);
+            ApiCallRcList answers = api.resourceDefinitionDelete(rscName);

Review comment:
       @rp- I have one question because I don't know how Linstor works. When you delete a volume that has snapshots are you deleting the snapshots as well?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-899457160


   @rp- yes, other plugins for example in case of ScaleIO and Ceph, the template is converted to RAW; the specific disk/file/drive is mounted on KVM host on which qemu-img is used to convert a guest template from a valid source (qcow2, raw...) to raw.
   
   /cc @sureshanaparti  (looping in Suresh who wrote the ScaleIO storage plugin)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-914926383


   are there any error logs? or how could I try to package myself?
   maybe a rebase on `main` is enough?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-917918644


   oh sorry, I forgot about unittests.
   Fixed, force pushed and also added a unittest for linstor there.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] nvazquez commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
nvazquez commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-919676829


   @blueorangutan test


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-920200772


   @blueorangutan test 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] GutoVeronezi commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
GutoVeronezi commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r630184204



##########
File path: plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
##########
@@ -0,0 +1,566 @@
+package com.cloud.hypervisor.kvm.storage;
+
+import com.linbit.linstor.api.ApiCallChecker;
+import com.linbit.linstor.api.ApiClient;
+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;
+import com.linbit.linstor.api.model.Properties;
+import com.linbit.linstor.api.model.ProviderKind;
+import com.linbit.linstor.api.model.Resource;
+import com.linbit.linstor.api.model.ResourceCreate;
+import com.linbit.linstor.api.model.ResourceDefinitionModify;
+import com.linbit.linstor.api.model.ResourceGroup;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
+import com.linbit.linstor.api.model.StoragePool;
+import com.linbit.linstor.api.model.VolumeDefinition;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+@StorageAdaptorInfo(storagePoolType=Storage.StoragePoolType.Linstor)
+public class LinstorStorageAdaptor implements StorageAdaptor {
+    private static final Logger s_logger = Logger.getLogger(LinstorStorageAdaptor.class);
+    private static final Map<String, KVMStoragePool> MapStorageUuidToStoragePool = new HashMap<>();
+
+    private DevelopersApi getLinstorAPI(KVMStoragePool pool) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(pool.getSourceHost());
+        return new DevelopersApi(client);
+    }
+
+    private String getLinstorRscName(String name) {
+        return "cs-" + name;
+    }
+
+    private String getHostname() {
+        // either there is already some function for that in the agent or a better way.
+        ProcessBuilder pb = new ProcessBuilder("hostname");
+        try
+        {
+            String result;
+            Process p = pb.start();
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+            StringJoiner sj = new StringJoiner(System.getProperty("line.separator"));
+            reader.lines().iterator().forEachRemaining(sj::add);
+            result = sj.toString();

Review comment:
       I did a little search and found a similar usage in `Agent.java` https://github.com/apache/cloudstack/blob/6b9f3fb5015c29bbfaee0b08220174541ce40ab0/agent/src/main/java/com/cloud/agent/Agent.java#L475-L478
   
   If we go deeper we may find another implementations.
   
   I would suggest you to create an agent utility and implement a method to retrieve the `hostname`, then use it instead of this whole method.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-878914612


   Packaging result: :heavy_check_mark: el7 :heavy_check_mark: el8 :heavy_check_mark: debian. SL-JID 537


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-956571240


   @rp- @Philipp-Reisner can you check a potential Linstor regression - https://markmail.org/message/tc4qkm2izzcjqahn 
   I'll tag/cc you in the email thread too.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] soptecsn commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
soptecsn commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-956585899


   We are trying to deploy linstor driver for primary storage and it is not wokring. We have configured 3 linstor nodes with ZFS, ACS 4.16
   
   First we get an error that linstor was not installed in the host, so we install it and the error disappear, we think is not correct that Host has to be installed with the software because they just to be a "client". But there aren't any documentation about this.
   
   Then, when that error disappear, we try to create a VM and the log in the host show this:
   
   INFO
    [kvm.storage.LinstorStorageAdaptor] (agentRequest-Handler-1:null)
    (logid:1fb3773c)
    Linstor: Created drbd device: /dev/zvol/Linstor-ST04/cs-c3bbbcbb-142a-4f28-bf1b-fd0748c7a6f7_00000
    ERROR [kvm.storage.LinstorStorageAdaptor] (agentRequest-Handler-1:null)
    (logid:1fb3773c)
    org.apache.cloudstack.utils.qemu.QemuImgException: qemu-img:
    /dev/zvol/Linstor-ST04/cs-c3bbbcbb-142a-4f28-bf1b-fd0748c7a6f7_00000:
    error while
    converting raw:
    Could not create file:
    No such file
   or
    directory
   
   When we go to the linstor dashboard, we see that Linstor creates a Resource Definition, then a resource into and finally the volumen, until this moment it all looking good, but inmediatly, deletes all resources created (resource definition, resource and volume).
   
   Linstor and Cloudstack are connecting fine when we add the primary storage.
   
   Error from the linstor side:
   
    
           ERROR REPORT 617B1236-00000-003014
   
   ============================================================
   
   Application:                        LINBIT® LINSTOR
   Module:                             Controller
   Version:                            1.15.0
   Build ID:                           7a46abd8becd8027357d06f5990a272224a71e81
   Build time:                         2021-09-23T06:21:28+00:00
   Error time:                         2021-11-01 20:59:15
   Node:                               linstor-st01
   Peer:                               RestClient(10.0.10.26; 'Swagger-Codegen/1.0.0/java')
   
   ============================================================
   
   Reported error:
   ===============
   
   Category:                           RuntimeException
   Class name:                         ApiRcException
   Class canonical name:               com.linbit.linstor.core.apicallhandler.response.ApiRcException
   Generated at:                       Method 'loadRscDfn', Source file 'CtrlApiDataLoader.java', Line #188
   
   Error message:                      Resource definition 'cs-c3bbbcbb-142a-4f28-bf1b-fd0748c7a6f7' not found.
   
   Error context:
       Resource definition 'cs-c3bbbcbb-142a-4f28-bf1b-fd0748c7a6f7' not found.
   
   Asynchronous stage backtrace:
   
       Error has been observed at the following site(s):
       	|_ checkpoint ⇢ Clone resource-definition
       Stack trace:
   
   Call backtrace:
   
       Method                                   Native Class:Line number
       loadRscDfn                               N      com.linbit.linstor.core.apicallhandler.controller.CtrlApiDataLoader:188
   
   Suppressed exception 1 of 1:
   ===============
   Category:                           RuntimeException
   Class name:                         OnAssemblyException
   Class canonical name:               reactor.core.publisher.FluxOnAssembly.OnAssemblyException
   Generated at:                       Method 'loadRscDfn', Source file 'CtrlApiDataLoader.java', Line #188
   
   Error message:                      
   Error has been observed at the following site(s):
   	|_ checkpoint ⇢ Clone resource-definition
   Stack trace:
   
   Error context:
       Resource definition 'cs-c3bbbcbb-142a-4f28-bf1b-fd0748c7a6f7' not found.
   
   Call backtrace:
   
       Method                                   Native Class:Line number
       loadRscDfn                               N      com.linbit.linstor.core.apicallhandler.controller.CtrlApiDataLoader:188
       loadRscDfn                               N      com.linbit.linstor.core.apicallhandler.controller.CtrlApiDataLoader:162
       cloneRscDfnInTransaction                 N      com.linbit.linstor.core.apicallhandler.controller.CtrlRscDfnApiCallHandler:850
       lambda$cloneRscDfn$6                     N      com.linbit.linstor.core.apicallhandler.controller.CtrlRscDfnApiCallHandler:723
       doInScope                                N      com.linbit.linstor.core.apicallhandler.ScopeRunner:147
       lambda$fluxInScope$0                     N      com.linbit.linstor.core.apicallhandler.ScopeRunner:75
       call                                     N      reactor.core.publisher.MonoCallable:91
       trySubscribeScalarMap                    N      reactor.core.publisher.FluxFlatMap:126
       subscribeOrReturn                        N      reactor.core.publisher.MonoFlatMapMany:49
       subscribe                                N      reactor.core.publisher.Flux:8343
       onNext                                   N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain:188
       request                                  N      reactor.core.publisher.Operators$ScalarSubscription:2344
       onSubscribe                              N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain:134
       subscribe                                N      reactor.core.publisher.MonoCurrentContext:35
       subscribe                                N      reactor.core.publisher.Flux:8357
       onNext                                   N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain:188
       request                                  N      reactor.core.publisher.Operators$ScalarSubscription:2344
       onSubscribe                              N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain:134
       subscribe                                N      reactor.core.publisher.MonoCurrentContext:35
       subscribe                                N      reactor.core.publisher.Flux:8357
       onNext                                   N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain:188
       onNext                                   N      reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber:121
       complete                                 N      reactor.core.publisher.Operators$MonoSubscriber:1782
       onComplete                               N      reactor.core.publisher.MonoCollect$CollectSubscriber:152
       onComplete                               N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner:252
       checkTerminated                          N      reactor.core.publisher.FluxFlatMap$FlatMapMain:838
       drainLoop                                N      reactor.core.publisher.FluxFlatMap$FlatMapMain:600
       drain                                    N      reactor.core.publisher.FluxFlatMap$FlatMapMain:580
       onComplete                               N      reactor.core.publisher.FluxFlatMap$FlatMapMain:457
       checkTerminated                          N      reactor.core.publisher.FluxFlatMap$FlatMapMain:838
       drainLoop                                N      reactor.core.publisher.FluxFlatMap$FlatMapMain:600
       innerComplete                            N      reactor.core.publisher.FluxFlatMap$FlatMapMain:909
       onComplete                               N      reactor.core.publisher.FluxFlatMap$FlatMapInner:1013
       onComplete                               N      reactor.core.publisher.FluxMap$MapSubscriber:136
       onComplete                               N      reactor.core.publisher.Operators$MultiSubscriptionSubscriber:2016
       onComplete                               N      reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber:78
       complete                                 N      reactor.core.publisher.FluxCreate$BaseSink:438
       drain                                    N      reactor.core.publisher.FluxCreate$BufferAsyncSink:784
       complete                                 N      reactor.core.publisher.FluxCreate$BufferAsyncSink:732
       drainLoop                                N      reactor.core.publisher.FluxCreate$SerializedSink:239
       drain                                    N      reactor.core.publisher.FluxCreate$SerializedSink:205
       complete                                 N      reactor.core.publisher.FluxCreate$SerializedSink:196
       apiCallComplete                          N      com.linbit.linstor.netcom.TcpConnectorPeer:455
       handleComplete                           N      com.linbit.linstor.proto.CommonMessageProcessor:363
       handleDataMessage                        N      com.linbit.linstor.proto.CommonMessageProcessor:287
       doProcessInOrderMessage                  N      com.linbit.linstor.proto.CommonMessageProcessor:235
       lambda$doProcessMessage$3                N      com.linbit.linstor.proto.CommonMessageProcessor:220
       subscribe                                N      reactor.core.publisher.FluxDefer:46
       subscribe                                N      reactor.core.publisher.Flux:8357
       onNext                                   N      reactor.core.publisher.FluxFlatMap$FlatMapMain:418
       drainAsync                               N      reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber:414
       drain                                    N      reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber:679
       onNext                                   N      reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber:243
       drainFused                               N      reactor.core.publisher.UnicastProcessor:286
       drain                                    N      reactor.core.publisher.UnicastProcessor:329
       onNext                                   N      reactor.core.publisher.UnicastProcessor:408
       next                                     N      reactor.core.publisher.FluxCreate$IgnoreSink:618
       drainLoop                                N      reactor.core.publisher.FluxCreate$SerializedSink:248
       next                                     N      reactor.core.publisher.FluxCreate$SerializedSink:168
       processInOrder                           N      com.linbit.linstor.netcom.TcpConnectorPeer:373
       doProcessMessage                         N      com.linbit.linstor.proto.CommonMessageProcessor:218
       lambda$processMessage$2                  N      com.linbit.linstor.proto.CommonMessageProcessor:164
       onNext                                   N      reactor.core.publisher.FluxPeek$PeekSubscriber:177
       runAsync                                 N      reactor.core.publisher.FluxPublishOn$PublishOnSubscriber:439
       run                                      N      reactor.core.publisher.FluxPublishOn$PublishOnSubscriber:526
       call                                     N      reactor.core.scheduler.WorkerTask:84
       call                                     N      reactor.core.scheduler.WorkerTask:37
       run                                      N      java.util.concurrent.FutureTask:264
       run                                      N      java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask:304
       runWorker                                N      java.util.concurrent.ThreadPoolExecutor:1128
       run                                      N      java.util.concurrent.ThreadPoolExecutor$Worker:628
       run                                      N      java.lang.Thread:829
   
   
   END OF ERROR REPORT.
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] nvazquez commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
nvazquez commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-957031297


   Hi @rp- @Philipp-Reisner can you please advise on the reported issue? Could be a configuration issue or a bug in CloudStack that needs fixing?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] nvazquez commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
nvazquez commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-918209354


   Thanks @rp- 
   @blueorangutan package


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-919677695


   @nvazquez a Trillian-Jenkins test job (centos7 mgmt + kvm-centos7) has been kicked to run smoke tests


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-920141767


   Packaging result: :heavy_check_mark: el7 :heavy_check_mark: el8 :heavy_check_mark: debian :heavy_check_mark: suse15. SL-JID 1273


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] andrijapanicsb commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
andrijapanicsb commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-888923813


   @rp- please solve the conflict 
   thanks


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-889030030


   @rp- can you fix the conflicts? Since smoketest round have passed, if you can provide details of your tests - we can do another round of review and regression smoketests. We can merge it if we've 2xreview lgtms and smoketests pass.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] sureshanaparti edited a comment on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
sureshanaparti edited a comment on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-842245607


   > > @rp- Please check if Linstor storage plugin supports all the functionalities mentioned in the cwiki. Can you add some storage references in cwiki ?
   > 
   > What do you mean with "storage references" ?
   
   @rp- I mean some references, for the docs to know about the Linstor storage. I found one mentioned in cwiki page, and  updated the references section.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-918241950


   Packaging result: :heavy_check_mark: el7 :heavy_check_mark: el8 :heavy_check_mark: debian :heavy_check_mark: suse15. SL-JID 1218


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-914136622


   Packaging result: :heavy_multiplication_x: el7 :heavy_multiplication_x: el8 :heavy_multiplication_x: debian :heavy_multiplication_x: suse15. SL-JID 1146


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-831965983


   Thanks for the PR @rp- is this ready for review/testing?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909059008






-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-912360056


   Since smoketests have passed, may I request one of the reviewer to help with a final review - maybe @sureshanaparti and @slavkap  since you've more experience around storage?
   @rp- we can merge this now as soon as we've 2xreviews/lgtm; any additional tests/results from you would also be great.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-920119323


   @rhtyd a Jenkins job has been kicked to build packages. I'll keep you posted as I make progress.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] sureshanaparti commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
sureshanaparti commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-920664163


   > Thanks @rp- @rhtyd - smoke tests passed, let's wait for @sureshanaparti's review
   
   @nvazquez reviewed the PR, LGTM.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] sureshanaparti commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
sureshanaparti commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r626394099



##########
File path: plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/LinstorPrimaryDataStoreLifeCycleImpl.java
##########
@@ -0,0 +1,327 @@
+/*
+ * 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.lifecycle;
+
+import javax.inject.Inject;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import com.cloud.agent.AgentManager;
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.CreateStoragePoolCommand;
+import com.cloud.agent.api.StoragePoolInfo;
+import com.cloud.capacity.CapacityManager;
+import com.cloud.dc.ClusterVO;
+import com.cloud.dc.dao.ClusterDao;
+import com.cloud.host.Host;
+import com.cloud.host.HostVO;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.resource.ResourceManager;
+import com.cloud.storage.Storage;
+import com.cloud.storage.StorageManager;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.StoragePoolAutomation;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.HostScope;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreLifeCycle;
+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.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
+import org.apache.cloudstack.storage.volume.datastore.PrimaryDataStoreHelper;
+import org.apache.log4j.Logger;
+
+public class LinstorPrimaryDataStoreLifeCycleImpl implements PrimaryDataStoreLifeCycle {
+    private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreLifeCycleImpl.class);
+
+    @Inject
+    private ClusterDao clusterDao;
+    @Inject
+    PrimaryDataStoreDao _primaryDataStoreDao;
+    @Inject
+    private ResourceManager resourceMgr;
+    @Inject
+    private StorageManager _storageMgr;
+    @Inject
+    PrimaryDataStoreHelper dataStoreHelper;
+    @Inject
+    private StoragePoolAutomation storagePoolAutomation;
+    @Inject
+    private CapacityManager _capacityMgr;
+    @Inject
+    AgentManager _agentMgr;
+
+    public LinstorPrimaryDataStoreLifeCycleImpl()
+    {
+    }
+
+    private static boolean isSupportedHypervisorType(HypervisorType hypervisorType) {
+        return HypervisorType.KVM.equals(hypervisorType);
+    }
+
+    @Override
+    public DataStore initialize(Map<String, Object> dsInfos) {
+        String url = (String) dsInfos.get("url");
+        Long zoneId = (Long) dsInfos.get("zoneId");
+        Long podId = (Long) dsInfos.get("podId");
+        Long clusterId = (Long) dsInfos.get("clusterId");
+        String storagePoolName = (String) dsInfos.get("name");
+        String providerName = (String) dsInfos.get("providerName");
+        String tags = (String) dsInfos.get("tags");
+        @SuppressWarnings("unchecked")
+        Map<String, String> details = (Map<String, String>) dsInfos.get("details");
+
+        final String resourceGroup = details.get(LinstorUtil.RSC_GROUP);
+
+        final String uuid = UUID.randomUUID().toString();
+
+        PrimaryDataStoreParameters parameters = new PrimaryDataStoreParameters();
+
+        // checks if primary datastore is clusterwide. If so, uses the clusterId to set
+        // the uuid and then sets the podId and clusterId parameters
+        if (clusterId != null) {
+            if (podId == null) {
+                throw new CloudRuntimeException("The Pod ID must be specified.");
+            }
+            if (zoneId == null) {
+                throw new CloudRuntimeException("The Zone ID must be specified.");
+            }
+            ClusterVO cluster = clusterDao.findById(clusterId);
+            s_logger.info("Linstor: Setting Linstor cluster-wide primary storage uuid to " + uuid);
+            parameters.setPodId(podId);
+            parameters.setClusterId(clusterId);
+
+            HypervisorType hypervisorType = cluster.getHypervisorType();
+
+            if (!isSupportedHypervisorType(hypervisorType)) {
+                throw new CloudRuntimeException(hypervisorType + " is not a supported hypervisor type.");
+            }
+        }
+
+        if (!url.contains("://")) {
+            url = "http://" + url;
+        }
+
+        URL controllerURL;
+        int port = 3370;
+        try
+        {
+            controllerURL = new URL(url);
+            if (!controllerURL.getProtocol().startsWith("http")) {
+                throw new IllegalArgumentException("Linstor controller URL wrong protocol: " + url);
+            }
+            if (!controllerURL.getPath().isEmpty()) {
+                throw new IllegalArgumentException("Linstor controller URL shouldn't have a path: " + url);
+            }
+            if (controllerURL.getPort() == -1) {
+                port = controllerURL.getProtocol().equals("https") ? 3371 : 3370;
+                url += ":" + port;
+            }
+        } catch (MalformedURLException e)
+        {
+            throw new IllegalArgumentException("Linstor controller URL is not valid: " + e);
+        }
+
+        long capacityBytes = LinstorUtil.getCapacityBytes(url, resourceGroup);
+
+        if (capacityBytes <= 0) {
+            throw new IllegalArgumentException("'capacityBytes' must be present and greater than 0.");
+        }
+
+        parameters.setHost(url);
+        parameters.setPort(port);
+        parameters.setPath(resourceGroup);
+        parameters.setType(Storage.StoragePoolType.Linstor);
+        parameters.setUuid(uuid);
+        parameters.setZoneId(zoneId);
+        parameters.setName(storagePoolName);
+        parameters.setProviderName(providerName);
+        parameters.setManaged(false);
+        parameters.setCapacityBytes(capacityBytes);
+        parameters.setUsedBytes(0);
+        parameters.setCapacityIops(0L);
+        parameters.setHypervisorType(HypervisorType.KVM);
+        parameters.setTags(tags);
+        parameters.setDetails(details);
+        parameters.setUserInfo(resourceGroup);
+
+        return dataStoreHelper.createPrimaryDataStore(parameters);
+    }
+
+    protected boolean createStoragePool(long hostId, StoragePool pool) {
+        s_logger.debug("creating pool " + pool.getName() + " on  host " + hostId);
+
+        if (pool.getPoolType() != Storage.StoragePoolType.Linstor) {
+            s_logger.warn(" Doesn't support storage pool type " + pool.getPoolType());
+            return false;
+        }
+        CreateStoragePoolCommand cmd = new CreateStoragePoolCommand(true, pool);
+        final Answer answer = _agentMgr.easySend(hostId, cmd);
+        if (answer != null && answer.getResult()) {
+            return true;
+        } else {
+            _primaryDataStoreDao.expunge(pool.getId());
+            String msg = answer != null ?
+                "Can not create storage pool through host " + hostId + " due to " + answer.getDetails() :
+                "Can not create storage pool through host " + hostId + " due to CreateStoragePoolCommand returns null";
+            s_logger.warn(msg);
+            throw new CloudRuntimeException(msg);
+        }
+    }
+
+    @Override
+    public boolean attachCluster(DataStore dataStore, ClusterScope scope) {
+        PrimaryDataStoreInfo primaryDataStoreInfo = (PrimaryDataStoreInfo) dataStore;
+
+        // check if there is at least one host up in this cluster

Review comment:
       Check if this datastore supports the hypervisor in the cluster or not. 




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] soptecsn commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
soptecsn commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-956585899


   We are trying to deploy linstor driver for primary storage and it is not wokring. We have configured 3 linstor nodes with ZFS, ACS 4.16
   
   First we get an error that linstor was not installed in the host, so we install it and the error disappear, we think is not correct that Host has to be installed with the software because they just to be a "client". But there aren't any documentation about this.
   
   Then, when that error disappear, we try to create a VM and the log in the host show this:
   
   INFO
    [kvm.storage.LinstorStorageAdaptor] (agentRequest-Handler-1:null)
    (logid:1fb3773c)
    Linstor: Created drbd device: /dev/zvol/Linstor-ST04/cs-c3bbbcbb-142a-4f28-bf1b-fd0748c7a6f7_00000
    ERROR [kvm.storage.LinstorStorageAdaptor] (agentRequest-Handler-1:null)
    (logid:1fb3773c)
    org.apache.cloudstack.utils.qemu.QemuImgException: qemu-img:
    /dev/zvol/Linstor-ST04/cs-c3bbbcbb-142a-4f28-bf1b-fd0748c7a6f7_00000:
    error while
    converting raw:
    Could not create file:
    No such file
   or
    directory
   
   When we go to the linstor dashboard, we see that Linstor creates a Resource Definition, then a resource into and finally the volumen, until this moment it all looking good, but inmediatly, deletes all resources created (resource definition, resource and volume).
   
   Linstor and Cloudstack are connecting fine when we add the primary storage.
   
   Error from the linstor side:
   
    
           ERROR REPORT 617B1236-00000-003014
   
   ============================================================
   
   Application:                        LINBIT® LINSTOR
   Module:                             Controller
   Version:                            1.15.0
   Build ID:                           7a46abd8becd8027357d06f5990a272224a71e81
   Build time:                         2021-09-23T06:21:28+00:00
   Error time:                         2021-11-01 20:59:15
   Node:                               linstor-st01
   Peer:                               RestClient(10.0.10.26; 'Swagger-Codegen/1.0.0/java')
   
   ============================================================
   
   Reported error:
   ===============
   
   Category:                           RuntimeException
   Class name:                         ApiRcException
   Class canonical name:               com.linbit.linstor.core.apicallhandler.response.ApiRcException
   Generated at:                       Method 'loadRscDfn', Source file 'CtrlApiDataLoader.java', Line #188
   
   Error message:                      Resource definition 'cs-c3bbbcbb-142a-4f28-bf1b-fd0748c7a6f7' not found.
   
   Error context:
       Resource definition 'cs-c3bbbcbb-142a-4f28-bf1b-fd0748c7a6f7' not found.
   
   Asynchronous stage backtrace:
   
       Error has been observed at the following site(s):
       	|_ checkpoint ⇢ Clone resource-definition
       Stack trace:
   
   Call backtrace:
   
       Method                                   Native Class:Line number
       loadRscDfn                               N      com.linbit.linstor.core.apicallhandler.controller.CtrlApiDataLoader:188
   
   Suppressed exception 1 of 1:
   ===============
   Category:                           RuntimeException
   Class name:                         OnAssemblyException
   Class canonical name:               reactor.core.publisher.FluxOnAssembly.OnAssemblyException
   Generated at:                       Method 'loadRscDfn', Source file 'CtrlApiDataLoader.java', Line #188
   
   Error message:                      
   Error has been observed at the following site(s):
   	|_ checkpoint ⇢ Clone resource-definition
   Stack trace:
   
   Error context:
       Resource definition 'cs-c3bbbcbb-142a-4f28-bf1b-fd0748c7a6f7' not found.
   
   Call backtrace:
   
       Method                                   Native Class:Line number
       loadRscDfn                               N      com.linbit.linstor.core.apicallhandler.controller.CtrlApiDataLoader:188
       loadRscDfn                               N      com.linbit.linstor.core.apicallhandler.controller.CtrlApiDataLoader:162
       cloneRscDfnInTransaction                 N      com.linbit.linstor.core.apicallhandler.controller.CtrlRscDfnApiCallHandler:850
       lambda$cloneRscDfn$6                     N      com.linbit.linstor.core.apicallhandler.controller.CtrlRscDfnApiCallHandler:723
       doInScope                                N      com.linbit.linstor.core.apicallhandler.ScopeRunner:147
       lambda$fluxInScope$0                     N      com.linbit.linstor.core.apicallhandler.ScopeRunner:75
       call                                     N      reactor.core.publisher.MonoCallable:91
       trySubscribeScalarMap                    N      reactor.core.publisher.FluxFlatMap:126
       subscribeOrReturn                        N      reactor.core.publisher.MonoFlatMapMany:49
       subscribe                                N      reactor.core.publisher.Flux:8343
       onNext                                   N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain:188
       request                                  N      reactor.core.publisher.Operators$ScalarSubscription:2344
       onSubscribe                              N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain:134
       subscribe                                N      reactor.core.publisher.MonoCurrentContext:35
       subscribe                                N      reactor.core.publisher.Flux:8357
       onNext                                   N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain:188
       request                                  N      reactor.core.publisher.Operators$ScalarSubscription:2344
       onSubscribe                              N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain:134
       subscribe                                N      reactor.core.publisher.MonoCurrentContext:35
       subscribe                                N      reactor.core.publisher.Flux:8357
       onNext                                   N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain:188
       onNext                                   N      reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber:121
       complete                                 N      reactor.core.publisher.Operators$MonoSubscriber:1782
       onComplete                               N      reactor.core.publisher.MonoCollect$CollectSubscriber:152
       onComplete                               N      reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner:252
       checkTerminated                          N      reactor.core.publisher.FluxFlatMap$FlatMapMain:838
       drainLoop                                N      reactor.core.publisher.FluxFlatMap$FlatMapMain:600
       drain                                    N      reactor.core.publisher.FluxFlatMap$FlatMapMain:580
       onComplete                               N      reactor.core.publisher.FluxFlatMap$FlatMapMain:457
       checkTerminated                          N      reactor.core.publisher.FluxFlatMap$FlatMapMain:838
       drainLoop                                N      reactor.core.publisher.FluxFlatMap$FlatMapMain:600
       innerComplete                            N      reactor.core.publisher.FluxFlatMap$FlatMapMain:909
       onComplete                               N      reactor.core.publisher.FluxFlatMap$FlatMapInner:1013
       onComplete                               N      reactor.core.publisher.FluxMap$MapSubscriber:136
       onComplete                               N      reactor.core.publisher.Operators$MultiSubscriptionSubscriber:2016
       onComplete                               N      reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber:78
       complete                                 N      reactor.core.publisher.FluxCreate$BaseSink:438
       drain                                    N      reactor.core.publisher.FluxCreate$BufferAsyncSink:784
       complete                                 N      reactor.core.publisher.FluxCreate$BufferAsyncSink:732
       drainLoop                                N      reactor.core.publisher.FluxCreate$SerializedSink:239
       drain                                    N      reactor.core.publisher.FluxCreate$SerializedSink:205
       complete                                 N      reactor.core.publisher.FluxCreate$SerializedSink:196
       apiCallComplete                          N      com.linbit.linstor.netcom.TcpConnectorPeer:455
       handleComplete                           N      com.linbit.linstor.proto.CommonMessageProcessor:363
       handleDataMessage                        N      com.linbit.linstor.proto.CommonMessageProcessor:287
       doProcessInOrderMessage                  N      com.linbit.linstor.proto.CommonMessageProcessor:235
       lambda$doProcessMessage$3                N      com.linbit.linstor.proto.CommonMessageProcessor:220
       subscribe                                N      reactor.core.publisher.FluxDefer:46
       subscribe                                N      reactor.core.publisher.Flux:8357
       onNext                                   N      reactor.core.publisher.FluxFlatMap$FlatMapMain:418
       drainAsync                               N      reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber:414
       drain                                    N      reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber:679
       onNext                                   N      reactor.core.publisher.FluxFlattenIterable$FlattenIterableSubscriber:243
       drainFused                               N      reactor.core.publisher.UnicastProcessor:286
       drain                                    N      reactor.core.publisher.UnicastProcessor:329
       onNext                                   N      reactor.core.publisher.UnicastProcessor:408
       next                                     N      reactor.core.publisher.FluxCreate$IgnoreSink:618
       drainLoop                                N      reactor.core.publisher.FluxCreate$SerializedSink:248
       next                                     N      reactor.core.publisher.FluxCreate$SerializedSink:168
       processInOrder                           N      com.linbit.linstor.netcom.TcpConnectorPeer:373
       doProcessMessage                         N      com.linbit.linstor.proto.CommonMessageProcessor:218
       lambda$processMessage$2                  N      com.linbit.linstor.proto.CommonMessageProcessor:164
       onNext                                   N      reactor.core.publisher.FluxPeek$PeekSubscriber:177
       runAsync                                 N      reactor.core.publisher.FluxPublishOn$PublishOnSubscriber:439
       run                                      N      reactor.core.publisher.FluxPublishOn$PublishOnSubscriber:526
       call                                     N      reactor.core.scheduler.WorkerTask:84
       call                                     N      reactor.core.scheduler.WorkerTask:37
       run                                      N      java.util.concurrent.FutureTask:264
       run                                      N      java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask:304
       runWorker                                N      java.util.concurrent.ThreadPoolExecutor:1128
       run                                      N      java.util.concurrent.ThreadPoolExecutor$Worker:628
       run                                      N      java.lang.Thread:829
   
   
   END OF ERROR REPORT.
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-918711809


   <b>Trillian test result (tid-2019)</b>
   Environment: kvm-centos7 (x2), Advanced Networking with Mgmt server 7
   Total time taken: 36596 seconds
   Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr4994-t2019-kvm-centos7.zip
   Smoke tests completed. 89 look OK, 0 have errors
   Only failed tests results shown below:
   
   
   Test | Result | Time (s) | Test File
   --- | --- | --- | ---
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-918835114


   @rp- can you fix the conflicts? I think this is otherwise good to merge. 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-911529370


   @blueorangutan test
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-879081172


   @rhtyd a Trillian-Jenkins test job (centos7 mgmt + kvm-centos7) has been kicked to run smoke tests


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909173356


   Thanks for updating @rp- I'll kick smoketests and happy to merge this if the PR passes smoketests. Can you share the tests you've performed with your plugin, since we don't have your env/setup or a Linstor setup we'll rely on you for functional testing. cc @andrijapanicsb 
   
   @blueorangutan package


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909059008






-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909168681






-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-919689083


   <b>Trillian Build Failed (tid-2048)<b/>


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-919710344


   If smoketests pass, I don't mind merging this. As we don't have the storage setup, we'll rely on @rp- for providing tests. 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-920118862


   @blueorangutan package 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-919811522


   @rhtyd 
   
   > LGTM, some changes needed @rp-:
   > * Add Apache License 2.0 headers on all new files
   
   Fixed/Added
   
   > * Can you check if changes in the cloudbyte storage plugin necessary
   
   Yes, unfortunately the cloudbyte plugin uses an old version of jersey client and I introduced a newer one which didn't build
   correctly if mixed. As far as I remember.
   I also assume it is in common interest to only have one version of jersey as dependency.
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-880740569


   Short update: So I added Linstor protocol to the wizard and was kinda able to get the cluster running, but this time I used ubuntu and ZFS as backend storage. Which sadly surfaced some problems with ZFS's parent/child relation problematic on volumes and snapshots.
   So after some thinking about this, we (Linstor) finally need a dedicated Clone/Copy resource API, which will handle the best possible clone solution for the used storage backend. So I have to first get this implemented into Linstor before I can continue on the cloudstack plugin.
   
   I can meanwhile fix the conflict, but I guess you want to check/test the version after I finished the above mentioned API.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] sureshanaparti commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
sureshanaparti commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-842245607


   > > @rp- Please check if Linstor storage plugin supports all the functionalities mentioned in the cwiki. Can you add some storage references in cwiki ?
   > 
   > What do you mean with "storage references" ?
   
   @rp- I mean some references to know about the Linstor storage. I found one mentioned in cwiki page, and  updated the references section.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd edited a comment on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd edited a comment on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-845882655


   @rp- here are some links for you for learning/references:  cc @Philipp-Reisner
   https://github.com/shapeblue/hackerbook (self-learning course)
   https://github.com/shapeblue/mbx (appliance based local ACS dev-test/CI/CD env)
   
   Here are some high-level lifecycle operations a typical storage provider can support: (it also depends if which ones of the following operations are even possible/allowed by the storage control plane)
   - Volumes: list (by name, id etc), create, resize, clone, delete, migrate, map/unmap/check-map (to a host), download volume (via secondary storage), get volume/disk statistics/metrics (usage, iops etc)
   - Volume Snapshots: list, take snapshot, revert snapshot, delete (note: this is a single volume/disk), download snapshot (via secondary storage) 
   - VM Snapshots: list, take snapshot, revert snapshot, delete (note: this may require a consistency group, i.e. a group of volumes/disks that are attached to a disk with or without memory, i.e. disk-only VM snapshot and disk+ram snapshots)
   - Template: create template from snapshot, create template from volume/disk
   - Storage pool (or Primary storage): list (by name, id etc), get pool statistics/metrics (usage, iops etc), create/add (with label)
   - Migration: can we migrate volumes from one storage pool (Linstore resource pool) to another pool (Linstore resource pool or other types of pool), can we migrate VM across hosts/clusters with/without Linstor storage
   
   Most of the above operations already have a high level CloudStack API, generally the storage provider/plugin implements the backend for these operations. In some cases, if an operation is not allowed (for example, raw-disk snapshots on KVM) you may need to add conditional checks or refactor the CloudStack service layers (API, managers, storage sub-system... which largely defines the policy) and hypervisor sub-system (KVM+storage specific storage data motion strategy, LibvirtStoragePoolDef, handling of config drive, VM/disk migration, KVM specific linstore storage pool manager + storage processor + storage adaptor classes, i.e. they implement how storage/hypervisor specific behaviour are executed).
   
   In general you would setup a dev-test env, and while adding new storage pool in CloudStack add a label to that pool (primary storage) and then create disk offerings with the label - this way you can force VM's root&data disks to be created on the pool. These are called storage tags (we've something similar called host tags which can be used to map compute/service offerings with hypervisor hosts). Lastly, you want to write Marvin-based integration tests specific to your storage provider.
   
   Example implementations for references:
   https://github.com/apache/cloudstack/pull/4304 (ScaleIO storage provider)
   https://github.com/apache/cloudstack/tree/master/plugins/storage/volume (all current storage providers)
   https://github.com/apache/cloudstack/tree/master/test/integration/plugins (examples of plugin-specific integration tests)
   https://cwiki.apache.org/confluence/display/CLOUDSTACK/Storage+subsystem+2.0 (CloudStack storage framework)
   
   Hope this helps.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-957147583


   @nvazquez I'm on it via a different communication channel
   
   But I suspect a configuration problem, I'll report back here what solved the case.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-911529948


   @rhtyd a Trillian-Jenkins test job (centos7 mgmt + kvm-centos7) has been kicked to run smoke tests


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] andrijapanicsb commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
andrijapanicsb commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-888926320


   @rp- being the lazy bastard I am (haven't read the design cwiki), do you have a list of ACS features what is possible (and what not) with this current/initial implementation? To have it outlined somewhere


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd merged pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd merged pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994


   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] GutoVeronezi commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
GutoVeronezi commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r630154264



##########
File path: plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
##########
@@ -0,0 +1,566 @@
+package com.cloud.hypervisor.kvm.storage;
+
+import com.linbit.linstor.api.ApiCallChecker;
+import com.linbit.linstor.api.ApiClient;
+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;
+import com.linbit.linstor.api.model.Properties;
+import com.linbit.linstor.api.model.ProviderKind;
+import com.linbit.linstor.api.model.Resource;
+import com.linbit.linstor.api.model.ResourceCreate;
+import com.linbit.linstor.api.model.ResourceDefinitionModify;
+import com.linbit.linstor.api.model.ResourceGroup;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
+import com.linbit.linstor.api.model.StoragePool;
+import com.linbit.linstor.api.model.VolumeDefinition;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+@StorageAdaptorInfo(storagePoolType=Storage.StoragePoolType.Linstor)
+public class LinstorStorageAdaptor implements StorageAdaptor {
+    private static final Logger s_logger = Logger.getLogger(LinstorStorageAdaptor.class);
+    private static final Map<String, KVMStoragePool> MapStorageUuidToStoragePool = new HashMap<>();
+
+    private DevelopersApi getLinstorAPI(KVMStoragePool pool) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(pool.getSourceHost());
+        return new DevelopersApi(client);
+    }
+
+    private String getLinstorRscName(String name) {
+        return "cs-" + name;
+    }
+
+    private String getHostname() {
+        // either there is already some function for that in the agent or a better way.
+        ProcessBuilder pb = new ProcessBuilder("hostname");
+        try
+        {
+            String result;
+            Process p = pb.start();
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+            StringJoiner sj = new StringJoiner(System.getProperty("line.separator"));
+            reader.lines().iterator().forEachRemaining(sj::add);
+            result = sj.toString();
+
+            p.waitFor();
+            p.destroy();
+            return result;
+        } catch (IOException | InterruptedException exc) {
+            throw new CloudRuntimeException("Unable to run hostname command.");
+        }
+    }
+
+    private void handleLinstorApiAnswers(ApiCallRcList answers, String excMessage) {
+        if (ApiCallChecker.hasError(answers)) {
+            for (ApiCallRc answer : answers) {
+                s_logger.error(answer.getMessage());
+            }
+            throw new CloudRuntimeException(excMessage);
+        }
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid, boolean refreshInfo) {
+        s_logger.debug("Linstor getStoragePool: " + uuid + " -> " + refreshInfo);
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMPhysicalDisk getPhysicalDisk(String name, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: getPhysicalDisk for " + name);
+        if (name == null) {
+            return null;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try {
+            final String rscName = getLinstorRscName(name);
+
+            List<VolumeDefinition> volumeDefs = api.volumeDefinitionList(rscName, null, null);
+            final long size = volumeDefs.isEmpty() ? 0 : volumeDefs.get(0).getSizeKib() * 1024;
+
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty()) {
+                final String devPath = resources.get(0).getVolumes().get(0).getDevicePath();
+                final KVMPhysicalDisk kvmDisk = new KVMPhysicalDisk(devPath, name, pool);
+                kvmDisk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
+                kvmDisk.setSize(size);
+                kvmDisk.setVirtualSize(size);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes for " + rscName);
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiExc) {
+            s_logger.error(apiExc);
+            throw new CloudRuntimeException(apiExc);
+        }
+    }
+
+    @Override
+    public KVMStoragePool createStoragePool(String name, String host, int port, String path, String userInfo,
+                                            Storage.StoragePoolType type)
+    {
+        s_logger.debug(String.format(
+            "Linstor createStoragePool: name: '%s', host: '%s', path: %s, userinfo: %s", name, host, path, userInfo));
+        LinstorStoragePool storagePool = new LinstorStoragePool(name, host, port, userInfo, type, this);
+
+        MapStorageUuidToStoragePool.put(name, storagePool);
+
+        return storagePool;
+    }
+
+    @Override
+    public boolean deleteStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.remove(uuid) != null;
+    }
+
+    @Override
+    public boolean deleteStoragePool(KVMStoragePool pool) {
+        return deleteStoragePool(pool.getUuid());
+    }
+
+    @Override
+    public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, QemuImg.PhysicalDiskFormat format,
+                                              Storage.ProvisioningType provisioningType, long size)
+    {
+        LinstorStoragePool lpool = (LinstorStoragePool) pool;
+        final DevelopersApi api = getLinstorAPI(pool);
+        ResourceGroupSpawn rgSpawn = new ResourceGroupSpawn();
+        final String rscName = getLinstorRscName(name);
+        rgSpawn.setResourceDefinitionName(rscName);
+        rgSpawn.addVolumeSizesItem(size / 1024); // linstor uses KiB
+        try {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = api.resourceGroupSpawn(lpool.getResourceGroup(), rgSpawn);
+            handleLinstorApiAnswers(answers, "Linstor: Unable to spawn resource.");
+
+            // query linstor for the device path
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            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);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiExc) {
+            throw new CloudRuntimeException(apiExc);
+        }
+    }
+
+    @Override
+    public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details)
+    {
+        s_logger.debug(String.format("Linstor: connectPhysicalDisk %s:%s -> %s", pool.getUuid(), volumePath, details));
+        if (volumePath == null) {
+            s_logger.warn("volumePath is null, ignoring");
+            return false;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try
+        {
+            final String rscName = getLinstorRscName(volumePath);
+            final String localNode = getHostname();
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.singletonList(localNode),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+
+            if (resources.isEmpty()) {
+                s_logger.info("Linstor: resource not created yet: " + rscName);
+                ResourceCreate rscCreate = new ResourceCreate();
+                Resource rsc = new Resource();
+                rsc.setNodeName(localNode);
+                rscCreate.setResource(rsc);
+                ApiCallRcList answers = api.resourceCreate(rscName, Collections.singletonList(rscCreate));
+                if (ApiCallChecker.hasError(answers)) {
+                    s_logger.error("Could not create resource " + rscName);
+                    throw new CloudRuntimeException(answers.get(0).getMessage());
+                }
+                // now diskful resource should be deployed
+            }
+            // keep diskless, maybe add an option, but I'm concerned about out of space situations
+//            else {
+//                final ResourceWithVolumes rsc = resources.get(0);
+//                if (rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS)) {
+//                    ApiCallRcList answers = api.resourceToggleDiskful(rscName, localNode);
+//                    if (ApiCallChecker.hasError(answers)) {
+//                        s_logger.error("Unable to toggle disk on " + rscName);
+//                        throw new CloudRuntimeException(answers.get(0).getMessage());
+//                    }
+//                }
+//            }
+
+            // allow 2 primaries for live migration, should be removed by disconnect on the other end
+            ResourceDefinitionModify rdm = new ResourceDefinitionModify();
+            Properties props = new Properties();
+            props.put("DrbdOptions/Net/allow-two-primaries", "yes");
+            rdm.setOverrideProps(props);
+            ApiCallRcList answers = api.resourceDefinitionModify(rscName, rdm);
+            if (ApiCallChecker.hasError(answers)) {
+                s_logger.error("Unable to set 'allow-two-primaries' on " + rscName);
+                throw new CloudRuntimeException(answers.get(0).getMessage());
+            }
+        } catch (ApiException apiExc) {
+            s_logger.error(apiExc);
+            throw new CloudRuntimeException(apiExc.getMessage());
+        }
+        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(String volumePath, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDisk " + pool.getUuid() + ":" + volumePath);
+        // TODO remove diskless/diskful on node
+        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDisk map");
+        // TODO remove diskless/diskful on node
+        return true;
+    }
+
+    private Optional<ResourceWithVolumes> getResourceByPath(final List<ResourceWithVolumes> resources, String path) {
+        return resources.stream()
+            .filter(rsc -> rsc.getVolumes().stream()
+                .anyMatch(v -> v.getDevicePath().equals(path)))
+            .findFirst();
+    }
+
+    /**
+     * disconnectPhysicalDiskByPath is called after e.g. a live migration.
+     * The problem is we have no idea just from the path to which linstor-controller
+     * this resource would belong to. But as it should be highly unlikely that someone
+     * uses more than one linstor-controller to manage resource on the same kvm host.
+     * We will just take the first stored storagepool.
+     */
+    @Override
+    public boolean disconnectPhysicalDiskByPath(String localPath)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDiskByPath " + localPath);
+        // get first storage pool from the map, as we don't now any better:
+        if (!MapStorageUuidToStoragePool.isEmpty())
+        {
+            String firstKey = MapStorageUuidToStoragePool.keySet().stream().findFirst().get();
+            final KVMStoragePool pool = MapStorageUuidToStoragePool.get(firstKey);
+
+            s_logger.debug("Linstor: Using storpool: " + pool.getUuid());
+            final DevelopersApi api = getLinstorAPI(pool);
+
+            try
+            {
+                List<ResourceWithVolumes> resources = api.viewResources(
+                    Collections.singletonList(getHostname()),
+                    null,
+                    null,
+                    null,
+                    null,
+                    null);
+
+                Optional<ResourceWithVolumes> rsc = getResourceByPath(resources, localPath);
+
+                if (rsc.isPresent())
+                {
+                    ResourceDefinitionModify rdm = new ResourceDefinitionModify();
+                    rdm.deleteProps(Collections.singletonList("DrbdOptions/Net/allow-two-primaries"));
+                    ApiCallRcList answers = api.resourceDefinitionModify(rsc.get().getName(), rdm);
+                    if (ApiCallChecker.hasError(answers))
+                    {
+                        s_logger.error("Failed to remove 'allow-two-primaries' on " + rsc.get().getName());
+                        throw new CloudRuntimeException(answers.get(0).getMessage());
+                    }
+
+                    return true;
+                }
+                s_logger.warn("Linstor: Couldn't find resource for this path: " + localPath);
+            } catch (ApiException apiExc) {
+                s_logger.error(apiExc);
+                throw new CloudRuntimeException(apiExc.getMessage());
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean deletePhysicalDisk(String name, KVMStoragePool pool, Storage.ImageFormat format)
+    {
+        s_logger.debug("Linstor: deletePhysicalDisk " + name);
+        final DevelopersApi api = getLinstorAPI(pool);
+
+        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);
+        } catch (ApiException apiExc) {
+            throw new CloudRuntimeException(apiExc);
+        }
+        return true;
+    }
+
+    @Override
+    public KVMPhysicalDisk createDiskFromTemplate(
+        KVMPhysicalDisk template,
+        String name,
+        QemuImg.PhysicalDiskFormat format,
+        Storage.ProvisioningType provisioningType,
+        long size,
+        KVMStoragePool destPool,
+        int timeout)
+    {
+        s_logger.info("Linstor: createDiskFromTemplate");
+        // if source is linstor lvm-thin snapshot we could snapshot and restore from, but do plain copy for now

Review comment:
       @rp- ok, understood.
   
   I know from `qemu` that there is a way to coalesce (merge) or collapse the snapshots ([cheatsheet](https://blog.programster.org/qemu-img-cheatsheet)). Could ZFS have anything like that?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r703270722



##########
File path: plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
##########
@@ -0,0 +1,720 @@
+// 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.driver;
+
+import com.linbit.linstor.api.ApiException;
+import com.linbit.linstor.api.CloneWaiter;
+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.ResourceDefinition;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted;
+import com.linbit.linstor.api.model.ResourceDefinitionCreate;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+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.VolumeDefinitionModify;
+
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.agent.api.to.DiskTO;
+import com.cloud.host.Host;
+import com.cloud.storage.ResizeVolumePayload;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.VolumeDetailVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.SnapshotDetailsDao;
+import com.cloud.storage.dao.SnapshotDetailsVO;
+import com.cloud.storage.dao.VMTemplatePoolDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.storage.command.CreateObjectAnswer;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
+import org.apache.cloudstack.storage.volume.VolumeObject;
+import org.apache.log4j.Logger;
+
+public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver {
+    private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class);
+    @Inject private PrimaryDataStoreDao _storagePoolDao;
+    @Inject private VolumeDao _volumeDao;
+    @Inject private VolumeDetailsDao _volumeDetailsDao;
+    @Inject private VMTemplatePoolDao _vmTemplatePoolDao;
+    @Inject private SnapshotDao _snapshotDao;
+    @Inject private SnapshotDetailsDao _snapshotDetailsDao;
+
+    public LinstorPrimaryDataStoreDriverImpl()
+    {
+    }
+
+    @Override
+    public Map<String, String> getCapabilities()
+    {
+        Map<String, String> mapCapabilities = new HashMap<>();
+
+        // Linstor will be restricted to only run on LVM-THIN and ZFS storage pools with ACS
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString(), Boolean.TRUE.toString());
+
+        // fetch if lvm-thin or ZFS
+        mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        // CAN_CREATE_VOLUME_FROM_SNAPSHOT see note from CAN_CREATE_VOLUME_FROM_VOLUME
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+        mapCapabilities.put(DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        return mapCapabilities;
+    }
+
+    @Override
+    public DataTO getTO(DataObject data)
+    {
+        return null;
+    }
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store)
+    {
+        return null;
+    }
+
+    @Override
+    public ChapInfo getChapInfo(DataObject dataObject)
+    {
+        return null;
+    }
+
+    @Override
+    public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+        return false;
+    }
+
+    @Override
+    public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+    }
+
+    @Override
+    public long getUsedBytes(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getUsedIops(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool)
+    {
+        return dataObject.getSize();
+    }
+
+    @Override
+    public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    private String getSnapshotName(String snapshotUuid) {
+        return LinstorUtil.RSC_PREFIX + snapshotUuid;
+    }
+
+    private void deleteResourceDefinition(StoragePoolVO storagePoolVO, String rscDefName)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceDefinitionDelete(rscDefName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete resource definition: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private void deleteSnapshot(@Nonnull DataStore dataStore, @Nonnull String rscDefName, @Nonnull String snapshotName)
+    {
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceSnapshotDelete(rscDefName, snapshotName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete snapshot: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private long getCsIdForCloning(long volumeId, String cloneOf) {
+        VolumeDetailVO volumeDetail = _volumeDetailsDao.findDetail(volumeId, cloneOf);
+
+        if (volumeDetail != null && volumeDetail.getValue() != null) {
+            return Long.parseLong(volumeDetail.getValue());
+        }
+
+        return Long.MIN_VALUE;
+    }
+
+    @Override
+    public void deleteAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("deleteAsync: " + dataObject.getType() + ";" + dataObject.getUuid());
+        String errMsg = null;
+
+        final long storagePoolId = dataStore.getId();
+        final StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
+
+        switch (dataObject.getType()) {
+            case VOLUME:
+            {
+                final VolumeInfo volumeInfo = (VolumeInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getPath();
+                deleteResourceDefinition(storagePool, rscName);
+
+                long usedBytes = storagePool.getUsedBytes();
+                long capacityIops = storagePool.getCapacityIops();
+
+                usedBytes -= volumeInfo.getSize();
+                if (volumeInfo.getMaxIops() != null)
+                    capacityIops += volumeInfo.getMaxIops();
+
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                storagePool.setCapacityIops(Math.max(0, capacityIops));
+
+                _storagePoolDao.update(storagePoolId, storagePool);
+            }
+                break;
+            case SNAPSHOT:
+                final SnapshotInfo snapshotInfo = (SnapshotInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + snapshotInfo.getBaseVolume().getPath();
+                deleteSnapshot(dataStore, rscName, getSnapshotName(snapshotInfo.getUuid()));
+                long usedBytes = storagePool.getUsedBytes() - snapshotInfo.getSize();
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                _storagePoolDao.update(storagePoolId, storagePool);
+                break;
+            default:
+                errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync";
+                s_logger.error(errMsg);
+        }
+
+        if (callback != null) {
+            CommandResult result = new CommandResult();
+            result.setResult(errMsg);
+
+            callback.complete(result);
+        }
+    }
+
+    private void logLinstorAnswer(@Nonnull ApiCallRc answer) {
+        if (answer.isError()) {
+            s_logger.error(answer.getMessage());
+        } else if (answer.isWarning()) {
+            s_logger.warn(answer.getMessage());
+        } else if (answer.isInfo()) {
+            s_logger.info(answer.getMessage());
+        }
+    }
+
+    private void logLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        answers.forEach(this::logLinstorAnswer);
+    }
+
+    private void checkLinstorAnswersThrow(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        if (answers.hasError())
+        {
+            String errMsg = answers.stream()
+                .filter(ApiCallRc::isError)
+                .findFirst()
+                .map(ApiCallRc::getMessage).orElse("Unknown linstor error");
+            throw new CloudRuntimeException(errMsg);
+        }
+    }
+
+    private String checkLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        return answers.stream().filter(ApiCallRc::isError).findFirst().map(ApiCallRc::getMessage).orElse(null);
+    }
+
+    private String getDeviceName(DevelopersApi linstorApi, String rscName) throws ApiException {
+        List<ResourceWithVolumes> resources = linstorApi.viewResources(
+            Collections.emptyList(),
+            Collections.singletonList(rscName),
+            Collections.emptyList(),
+            null,
+            null,
+            null);
+        if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty())
+        {
+            s_logger.info("Linstor: Created drbd device: " + resources.get(0).getVolumes().get(0).getDevicePath());
+            return resources.get(0).getVolumes().get(0).getDevicePath();
+        } else
+        {
+            s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+            throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+        }
+    }
+
+    private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+
+        ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn();
+        final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
+        rscGrpSpawn.setResourceDefinitionName(rscName);
+        rscGrpSpawn.addVolumeSizesItem(vol.getSize() / 1024);
+
+        try
+        {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = linstorApi.resourceGroupSpawn(rscGrp, rscGrpSpawn);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String cloneResource(long csCloneId, VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        // get the cached template on this storage
+        VMTemplateStoragePoolVO tmplPoolRef = _vmTemplatePoolDao.findByPoolTemplate(
+            storagePoolVO.getId(), csCloneId, null);
+
+        if (tmplPoolRef != null) {
+            final String cloneRes = LinstorUtil.RSC_PREFIX + tmplPoolRef.getLocalDownloadPath();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+            try {
+                s_logger.debug("Clone resource definition " + cloneRes + " to " + rscName);
+                ResourceDefinitionCloneRequest cloneRequest = new ResourceDefinitionCloneRequest();
+                cloneRequest.setName(rscName);
+                ResourceDefinitionCloneStarted cloneStarted = linstorApi.resourceDefinitionClone(
+                    cloneRes, cloneRequest);
+
+                checkLinstorAnswersThrow(cloneStarted.getMessages());
+
+                if (!CloneWaiter.waitFor(linstorApi, cloneStarted)) {
+                    throw new CloudRuntimeException("Clone for resource " + rscName + " failed.");
+                }
+
+                return getDeviceName(linstorApi, rscName);
+            } catch (ApiException apiEx) {
+                s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+                throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+            }
+        } else {
+            throw new CloudRuntimeException(
+                "Unable to find Linstor resource for the following template data-object ID: " + csCloneId);
+        }
+    }
+
+    private String createResourceFromSnapshot(long csSnapshotId, String rscName, StoragePoolVO storagePoolVO) {
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+        final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        SnapshotVO snapshotVO = _snapshotDao.findById(csSnapshotId);
+        String snapName = LinstorUtil.RSC_PREFIX + snapshotVO.getUuid();
+        VolumeVO volumeVO = _volumeDao.findById(snapshotVO.getVolumeId());
+        String cloneRes = LinstorUtil.RSC_PREFIX + volumeVO.getPath();
+
+        try
+        {
+            s_logger.debug("Create new resource definition: " + rscName);
+            ResourceDefinitionCreate rdCreate = new ResourceDefinitionCreate();
+            ResourceDefinition rd = new ResourceDefinition();
+            rd.setName(rscName);
+            rd.setResourceGroupName(rscGrp);
+            rdCreate.setResourceDefinition(rd);
+            ApiCallRcList answers = linstorApi.resourceDefinitionCreate(rdCreate);
+            checkLinstorAnswersThrow(answers);
+
+            SnapshotRestore snapshotRestore = new SnapshotRestore();
+            snapshotRestore.toResource(rscName);
+
+            s_logger.debug("Create new volume definition for snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotsRestoreVolumeDefinition(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            // restore snapshot to new resource
+            s_logger.debug("Restore resource from snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotRestore(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String createVolume(VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = getCsIdForCloning(volumeInfo.getId(), "cloneOfSnapshot");
+        long csTemplateId = getCsIdForCloning(volumeInfo.getId(), "cloneOfTemplate");
+
+        if (csSnapshotId > 0) {
+            return createResourceFromSnapshot(csSnapshotId, LinstorUtil.RSC_PREFIX + volumeInfo.getUuid(), storagePoolVO);
+        } else if (csTemplateId > 0) {
+            return cloneResource(csTemplateId, volumeInfo, storagePoolVO);
+        } else {
+            return createResource(volumeInfo, storagePoolVO);
+        }
+    }
+
+    private void handleSnapshotDetails(long csSnapshotId, String name, String value) {
+        _snapshotDetailsDao.removeDetail(csSnapshotId, name);
+        SnapshotDetailsVO snapshotDetails = new SnapshotDetailsVO(csSnapshotId, name, value, false);
+        _snapshotDetailsDao.persist(snapshotDetails);
+    }
+
+    private void addTempVolumeToDb(long csSnapshotId, String tempVolumeName) {
+        // TEMP_VOLUME_ID is needed, to find which temporary resource should be deleted after copying it on agent side
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, LinstorUtil.RSC_PREFIX + tempVolumeName);
+        // the iqn will be used on the agent side to copy from, even though linstor doesn't have anything to do with IQN
+        handleSnapshotDetails(csSnapshotId, DiskTO.IQN, tempVolumeName);
+    }
+
+    private void removeTempVolumeFromDb(long csSnapshotId) {
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+        if (snapshotDetails == null || snapshotDetails.getValue() == null) {
+            throw new CloudRuntimeException(
+                "'removeTempVolumeId' should not be invoked unless " + LinstorUtil.TEMP_VOLUME_ID + " exists.");
+        }
+
+        String originalVolumeId = snapshotDetails.getValue();
+
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, originalVolumeId);
+
+        _snapshotDetailsDao.remove(snapshotDetails.getId());
+    }
+
+    private void createVolumeFromSnapshot(SnapshotInfo snapshotInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = snapshotInfo.getId();
+
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, "tempVolume");
+
+        if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("create"))
+        {
+            final String csName = "Temp-" + snapshotInfo.getUuid();
+            final String tempRscName = LinstorUtil.RSC_PREFIX + csName;
+            createResourceFromSnapshot(csSnapshotId, tempRscName, storagePoolVO);
+
+            s_logger.debug("Temp resource created: " + tempRscName);
+            addTempVolumeToDb(csSnapshotId, csName);
+        }
+        else if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("delete"))
+        {
+            snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+            deleteResourceDefinition(storagePoolVO, snapshotDetails.getValue());
+
+            s_logger.debug("Temp resource deleted: " + snapshotDetails.getValue());
+            removeTempVolumeFromDb(csSnapshotId);
+        }
+        else {
+            throw new CloudRuntimeException("Invalid state in 'createVolumeFromSnapshot(SnapshotInfo, StoragePoolVO)'");
+        }
+    }
+
+    @Override
+    public void createAsync(DataStore dataStore, DataObject vol, AsyncCompletionCallback<CreateCmdResult> callback)
+    {
+        String devPath = null;
+        String errMsg = null;
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+
+        try
+        {
+            switch (vol.getType())
+            {
+                case VOLUME:
+                    VolumeInfo volumeInfo = (VolumeInfo) vol;
+                    VolumeVO volume = _volumeDao.findById(volumeInfo.getId());
+                    s_logger.debug("createAsync - creating volume");
+                    devPath = createVolume(volumeInfo, storagePool);
+                    volume.setFolder("/dev/");
+                    volume.setPoolId(storagePool.getId());
+                    volume.setUuid(vol.getUuid());
+                    volume.setPath(vol.getUuid());
+
+                    _volumeDao.update(volume.getId(), volume);
+                    break;
+                case SNAPSHOT:
+                    s_logger.debug("createAsync - SNAPSHOT");
+                    createVolumeFromSnapshot((SnapshotInfo) vol, storagePool);
+                    break;
+                case TEMPLATE:
+                    errMsg = "creating template - not supported";
+                    s_logger.error("createAsync - " + errMsg);
+                    break;
+                default:
+                    errMsg = "Invalid DataObjectType (" + vol.getType() + ") passed to createAsync";
+                    s_logger.error(errMsg);
+            }
+        } catch (Exception ex)
+        {
+            errMsg = ex.getMessage();
+
+            s_logger.error("createAsync: " + errMsg);
+            if (callback == null)
+            {
+                throw ex;
+            }
+        }
+
+        if (callback != null)
+        {
+            CreateCmdResult result = new CreateCmdResult(devPath, new Answer(null, errMsg == null, errMsg));
+            result.setResult(errMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public void revertSnapshot(
+        SnapshotInfo snapshot,
+        SnapshotInfo snapshotOnPrimaryStore,
+        AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("Linstor: revertSnapshot");
+        final VolumeInfo volumeInfo = snapshot.getBaseVolume();
+        VolumeVO volumeVO = _volumeDao.findById(volumeInfo.getId());
+        if (volumeVO == null || volumeVO.getRemoved() != null) {
+            CommandResult commandResult = new CommandResult();
+            commandResult.setResult("The volume that the snapshot belongs to no longer exists.");
+            callback.complete(commandResult);
+            return;
+        }
+
+        String resultMsg;
+        try {
+            final StoragePool pool = (StoragePool) snapshot.getDataStore();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final String snapName = LinstorUtil.RSC_PREFIX + snapshot.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(pool.getHostAddress());
+
+            ApiCallRcList answers = linstorApi.resourceSnapshotRollback(rscName, snapName);
+            resultMsg = checkLinstorAnswers(answers);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            resultMsg = apiEx.getBestMessage();
+        }
+
+        if (callback != null)
+        {
+            CommandResult result = new CommandResult();
+            result.setResult(resultMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public boolean canCopy(DataObject srcData, DataObject destData)
+    {
+        return false;
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback)

Review comment:
       I added the notify call, but it didn't make a difference for my alpine guest.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-831973229


   @rhtyd a Jenkins job has been kicked to build packages. I'll keep you posted as I make progress.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-919768657


   @rhtyd a Trillian-Jenkins test job (centos7 mgmt + xenserver-71) has been kicked to run smoke tests


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-920201123


   @rhtyd a Trillian-Jenkins test job (centos7 mgmt + kvm-centos7) has been kicked to run smoke tests


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r629885536



##########
File path: plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java
##########
@@ -0,0 +1,566 @@
+package com.cloud.hypervisor.kvm.storage;
+
+import com.linbit.linstor.api.ApiCallChecker;
+import com.linbit.linstor.api.ApiClient;
+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;
+import com.linbit.linstor.api.model.Properties;
+import com.linbit.linstor.api.model.ProviderKind;
+import com.linbit.linstor.api.model.Resource;
+import com.linbit.linstor.api.model.ResourceCreate;
+import com.linbit.linstor.api.model.ResourceDefinitionModify;
+import com.linbit.linstor.api.model.ResourceGroup;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+import com.linbit.linstor.api.model.ResourceWithVolumes;
+import com.linbit.linstor.api.model.StoragePool;
+import com.linbit.linstor.api.model.VolumeDefinition;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.StringJoiner;
+
+import com.cloud.storage.Storage;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.utils.qemu.QemuImg;
+import org.apache.cloudstack.utils.qemu.QemuImgException;
+import org.apache.cloudstack.utils.qemu.QemuImgFile;
+import org.apache.log4j.Logger;
+import org.libvirt.LibvirtException;
+
+@StorageAdaptorInfo(storagePoolType=Storage.StoragePoolType.Linstor)
+public class LinstorStorageAdaptor implements StorageAdaptor {
+    private static final Logger s_logger = Logger.getLogger(LinstorStorageAdaptor.class);
+    private static final Map<String, KVMStoragePool> MapStorageUuidToStoragePool = new HashMap<>();
+
+    private DevelopersApi getLinstorAPI(KVMStoragePool pool) {
+        ApiClient client = Configuration.getDefaultApiClient();
+        client.setBasePath(pool.getSourceHost());
+        return new DevelopersApi(client);
+    }
+
+    private String getLinstorRscName(String name) {
+        return "cs-" + name;
+    }
+
+    private String getHostname() {
+        // either there is already some function for that in the agent or a better way.
+        ProcessBuilder pb = new ProcessBuilder("hostname");
+        try
+        {
+            String result;
+            Process p = pb.start();
+            final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+            StringJoiner sj = new StringJoiner(System.getProperty("line.separator"));
+            reader.lines().iterator().forEachRemaining(sj::add);
+            result = sj.toString();
+
+            p.waitFor();
+            p.destroy();
+            return result;
+        } catch (IOException | InterruptedException exc) {
+            throw new CloudRuntimeException("Unable to run hostname command.");
+        }
+    }
+
+    private void handleLinstorApiAnswers(ApiCallRcList answers, String excMessage) {
+        if (ApiCallChecker.hasError(answers)) {
+            for (ApiCallRc answer : answers) {
+                s_logger.error(answer.getMessage());
+            }
+            throw new CloudRuntimeException(excMessage);
+        }
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMStoragePool getStoragePool(String uuid, boolean refreshInfo) {
+        s_logger.debug("Linstor getStoragePool: " + uuid + " -> " + refreshInfo);
+        return MapStorageUuidToStoragePool.get(uuid);
+    }
+
+    @Override
+    public KVMPhysicalDisk getPhysicalDisk(String name, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: getPhysicalDisk for " + name);
+        if (name == null) {
+            return null;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try {
+            final String rscName = getLinstorRscName(name);
+
+            List<VolumeDefinition> volumeDefs = api.volumeDefinitionList(rscName, null, null);
+            final long size = volumeDefs.isEmpty() ? 0 : volumeDefs.get(0).getSizeKib() * 1024;
+
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty()) {
+                final String devPath = resources.get(0).getVolumes().get(0).getDevicePath();
+                final KVMPhysicalDisk kvmDisk = new KVMPhysicalDisk(devPath, name, pool);
+                kvmDisk.setFormat(QemuImg.PhysicalDiskFormat.RAW);
+                kvmDisk.setSize(size);
+                kvmDisk.setVirtualSize(size);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes for " + rscName);
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiExc) {
+            s_logger.error(apiExc);
+            throw new CloudRuntimeException(apiExc);
+        }
+    }
+
+    @Override
+    public KVMStoragePool createStoragePool(String name, String host, int port, String path, String userInfo,
+                                            Storage.StoragePoolType type)
+    {
+        s_logger.debug(String.format(
+            "Linstor createStoragePool: name: '%s', host: '%s', path: %s, userinfo: %s", name, host, path, userInfo));
+        LinstorStoragePool storagePool = new LinstorStoragePool(name, host, port, userInfo, type, this);
+
+        MapStorageUuidToStoragePool.put(name, storagePool);
+
+        return storagePool;
+    }
+
+    @Override
+    public boolean deleteStoragePool(String uuid) {
+        return MapStorageUuidToStoragePool.remove(uuid) != null;
+    }
+
+    @Override
+    public boolean deleteStoragePool(KVMStoragePool pool) {
+        return deleteStoragePool(pool.getUuid());
+    }
+
+    @Override
+    public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool, QemuImg.PhysicalDiskFormat format,
+                                              Storage.ProvisioningType provisioningType, long size)
+    {
+        LinstorStoragePool lpool = (LinstorStoragePool) pool;
+        final DevelopersApi api = getLinstorAPI(pool);
+        ResourceGroupSpawn rgSpawn = new ResourceGroupSpawn();
+        final String rscName = getLinstorRscName(name);
+        rgSpawn.setResourceDefinitionName(rscName);
+        rgSpawn.addVolumeSizesItem(size / 1024); // linstor uses KiB
+        try {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = api.resourceGroupSpawn(lpool.getResourceGroup(), rgSpawn);
+            handleLinstorApiAnswers(answers, "Linstor: Unable to spawn resource.");
+
+            // query linstor for the device path
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.emptyList(),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+            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);
+                return kvmDisk;
+            } else {
+                s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+                throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+            }
+        } catch (ApiException apiExc) {
+            throw new CloudRuntimeException(apiExc);
+        }
+    }
+
+    @Override
+    public boolean connectPhysicalDisk(String volumePath, KVMStoragePool pool, Map<String, String> details)
+    {
+        s_logger.debug(String.format("Linstor: connectPhysicalDisk %s:%s -> %s", pool.getUuid(), volumePath, details));
+        if (volumePath == null) {
+            s_logger.warn("volumePath is null, ignoring");
+            return false;
+        }
+
+        final DevelopersApi api = getLinstorAPI(pool);
+        try
+        {
+            final String rscName = getLinstorRscName(volumePath);
+            final String localNode = getHostname();
+            List<ResourceWithVolumes> resources = api.viewResources(
+                Collections.singletonList(localNode),
+                Collections.singletonList(rscName),
+                Collections.emptyList(),
+                null,
+                null,
+                null);
+
+            if (resources.isEmpty()) {
+                s_logger.info("Linstor: resource not created yet: " + rscName);
+                ResourceCreate rscCreate = new ResourceCreate();
+                Resource rsc = new Resource();
+                rsc.setNodeName(localNode);
+                rscCreate.setResource(rsc);
+                ApiCallRcList answers = api.resourceCreate(rscName, Collections.singletonList(rscCreate));
+                if (ApiCallChecker.hasError(answers)) {
+                    s_logger.error("Could not create resource " + rscName);
+                    throw new CloudRuntimeException(answers.get(0).getMessage());
+                }
+                // now diskful resource should be deployed
+            }
+            // keep diskless, maybe add an option, but I'm concerned about out of space situations
+//            else {
+//                final ResourceWithVolumes rsc = resources.get(0);
+//                if (rsc.getFlags() != null && rsc.getFlags().contains(ApiConsts.FLAG_DISKLESS)) {
+//                    ApiCallRcList answers = api.resourceToggleDiskful(rscName, localNode);
+//                    if (ApiCallChecker.hasError(answers)) {
+//                        s_logger.error("Unable to toggle disk on " + rscName);
+//                        throw new CloudRuntimeException(answers.get(0).getMessage());
+//                    }
+//                }
+//            }
+
+            // allow 2 primaries for live migration, should be removed by disconnect on the other end
+            ResourceDefinitionModify rdm = new ResourceDefinitionModify();
+            Properties props = new Properties();
+            props.put("DrbdOptions/Net/allow-two-primaries", "yes");
+            rdm.setOverrideProps(props);
+            ApiCallRcList answers = api.resourceDefinitionModify(rscName, rdm);
+            if (ApiCallChecker.hasError(answers)) {
+                s_logger.error("Unable to set 'allow-two-primaries' on " + rscName);
+                throw new CloudRuntimeException(answers.get(0).getMessage());
+            }
+        } catch (ApiException apiExc) {
+            s_logger.error(apiExc);
+            throw new CloudRuntimeException(apiExc.getMessage());
+        }
+        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(String volumePath, KVMStoragePool pool)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDisk " + pool.getUuid() + ":" + volumePath);
+        // TODO remove diskless/diskful on node
+        return true;
+    }
+
+    @Override
+    public boolean disconnectPhysicalDisk(Map<String, String> volumeToDisconnect)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDisk map");
+        // TODO remove diskless/diskful on node
+        return true;
+    }
+
+    private Optional<ResourceWithVolumes> getResourceByPath(final List<ResourceWithVolumes> resources, String path) {
+        return resources.stream()
+            .filter(rsc -> rsc.getVolumes().stream()
+                .anyMatch(v -> v.getDevicePath().equals(path)))
+            .findFirst();
+    }
+
+    /**
+     * disconnectPhysicalDiskByPath is called after e.g. a live migration.
+     * The problem is we have no idea just from the path to which linstor-controller
+     * this resource would belong to. But as it should be highly unlikely that someone
+     * uses more than one linstor-controller to manage resource on the same kvm host.
+     * We will just take the first stored storagepool.
+     */
+    @Override
+    public boolean disconnectPhysicalDiskByPath(String localPath)
+    {
+        s_logger.debug("Linstor: disconnectPhysicalDiskByPath " + localPath);
+        // get first storage pool from the map, as we don't now any better:
+        if (!MapStorageUuidToStoragePool.isEmpty())
+        {
+            String firstKey = MapStorageUuidToStoragePool.keySet().stream().findFirst().get();
+            final KVMStoragePool pool = MapStorageUuidToStoragePool.get(firstKey);
+
+            s_logger.debug("Linstor: Using storpool: " + pool.getUuid());
+            final DevelopersApi api = getLinstorAPI(pool);
+
+            try
+            {
+                List<ResourceWithVolumes> resources = api.viewResources(
+                    Collections.singletonList(getHostname()),
+                    null,
+                    null,
+                    null,
+                    null,
+                    null);
+
+                Optional<ResourceWithVolumes> rsc = getResourceByPath(resources, localPath);
+
+                if (rsc.isPresent())
+                {
+                    ResourceDefinitionModify rdm = new ResourceDefinitionModify();
+                    rdm.deleteProps(Collections.singletonList("DrbdOptions/Net/allow-two-primaries"));
+                    ApiCallRcList answers = api.resourceDefinitionModify(rsc.get().getName(), rdm);
+                    if (ApiCallChecker.hasError(answers))
+                    {
+                        s_logger.error("Failed to remove 'allow-two-primaries' on " + rsc.get().getName());
+                        throw new CloudRuntimeException(answers.get(0).getMessage());
+                    }
+
+                    return true;
+                }
+                s_logger.warn("Linstor: Couldn't find resource for this path: " + localPath);
+            } catch (ApiException apiExc) {
+                s_logger.error(apiExc);
+                throw new CloudRuntimeException(apiExc.getMessage());
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean deletePhysicalDisk(String name, KVMStoragePool pool, Storage.ImageFormat format)
+    {
+        s_logger.debug("Linstor: deletePhysicalDisk " + name);
+        final DevelopersApi api = getLinstorAPI(pool);
+
+        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);
+        } catch (ApiException apiExc) {
+            throw new CloudRuntimeException(apiExc);
+        }
+        return true;
+    }
+
+    @Override
+    public KVMPhysicalDisk createDiskFromTemplate(
+        KVMPhysicalDisk template,
+        String name,
+        QemuImg.PhysicalDiskFormat format,
+        Storage.ProvisioningType provisioningType,
+        long size,
+        KVMStoragePool destPool,
+        int timeout)
+    {
+        s_logger.info("Linstor: createDiskFromTemplate");
+        // if source is linstor lvm-thin snapshot we could snapshot and restore from, but do plain copy for now

Review comment:
       well it kinda is explained in the comment.
   Linstor only supports snapshots from thin-lvm and ZFS backends, but with ZFS there exists a parent-child relation between snapshots. This means it could happen that you wan't to delete an old volume which once was your base of a template, but you can't deleted it, because you have several snapshots from it. (only way is to reverse the parent-child relation)
   So from this experience we decided in other plugins to do rather plain volume copies because none of those problems would arise.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] sureshanaparti commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
sureshanaparti commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-880734057


   Hi @rp- can you please fix the conflicts. thanks.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] sureshanaparti commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
sureshanaparti commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-842245607


   > > @rp- Please check if Linstor storage plugin supports all the functionalities mentioned in the cwiki. Can you add some storage references in cwiki ?
   > 
   > What do you mean with "storage references" ?
   
   @rp- I mean some references to know about the Linstor storage. I found one mentioned in cwiki page, and  updated the references section.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] shwstppr closed pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
shwstppr closed pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994


   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909202809


   @rhtyd a Trillian-Jenkins test job (centos7 mgmt + kvm-centos7) has been kicked to run smoke tests


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909174240


   @rhtyd a Jenkins job has been kicked to build packages. I'll keep you posted as I make progress.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-914354719


   Packaging result: :heavy_multiplication_x: el7 :heavy_multiplication_x: el8 :heavy_multiplication_x: debian :heavy_multiplication_x: suse15. SL-JID 1149


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-832775296


   Packaging result: :heavy_check_mark: centos7 :heavy_check_mark: centos8 :heavy_check_mark: debian. SL-JID 523


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909200357


   Packaging result: :heavy_check_mark: el7 :heavy_check_mark: el8 :heavy_check_mark: debian :heavy_check_mark: suse15. SL-JID 1078


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-831990931


   Packaging result: :heavy_check_mark: centos7 :heavy_check_mark: centos8 :heavy_check_mark: debian. SL-JID 514


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] slavkap commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
slavkap commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-870390972


   Hi @rp-, I have faced such a problem when there are merged PRs with new tables/added or renamed columns between your commits. You could check what's new here `engine/schema/src/main/resources/META-INF/db/schema-41510to41600.sql`. But someone with more experience in this maybe should tell


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] slavkap commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
slavkap commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-870426483


   That's great @rp- :) I think that there is no exception because of the inconsistency in DB with the renamed columns, and the SQL schema is not executed because you don't do an upgrade


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-920051417


   <b>Trillian test result (tid-2047)</b>
   Environment: kvm-centos7 (x2), Advanced Networking with Mgmt server 7
   Total time taken: 34981 seconds
   Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr4994-t2047-kvm-centos7.zip
   Smoke tests completed. 89 look OK, 0 have errors
   Only failed tests results shown below:
   
   
   Test | Result | Time (s) | Test File
   --- | --- | --- | ---
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-870349841


   The force push 11 days ago, was my new version with snapshots working, but before I announce it here, I wanted to give it a final test in my development installation. But suddenly as I wanted to start the rebased version, cloudstack stopped working on my end.
   Last week I was on vacation till today and I just retried a fresh rebased version, but cloudstack still doesn't want to come online:
   [management-server.log](https://github.com/apache/cloudstack/files/6731497/management-server.log)
   
   No error in the log and on the web interface port I get `HTTP ERROR 503 Service Unavailable`, maybe you know whats wrong here?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-918210096


   @nvazquez a Jenkins job has been kicked to build packages. I'll keep you posted as I make progress.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-920124708


   <b>Trillian test result (tid-2049)</b>
   Environment: kvm-centos7 (x2), Advanced Networking with Mgmt server 7
   Total time taken: 37354 seconds
   Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr4994-t2049-kvm-centos7.zip
   Smoke tests completed. 89 look OK, 0 have errors
   Only failed tests results shown below:
   
   
   Test | Result | Time (s) | Test File
   --- | --- | --- | ---
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] sureshanaparti commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
sureshanaparti commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-880772931


   > @rhtyd
   > 
   > > For example, you can run and share results of your smoketests in your env (or using virtualised https://github.com/shapeblue/mbx) with Linstor storage with KVM as the only primary storage. Or a set of manual tests. We can merge based on your provided test results and regression smoketests, and after all review items are addressed.
   > 
   > So I'm in the middle of setup a new cluster with just Linstor as primary storage, and in the setup wizard I noticed that only the options for (nfs, SharedMountPoint, RBD, CLVM, Gluster). So why are here solidfire, datera and others missing and is it ok to add the default nfs primary storage and afteward deleted it and add just a Linstor one? Or should I be able to already pick Linstor here?
   
   @rp- missed to reply for the second part. In case you have any issues with changes to setup wizard (zone creation wizard) for Linstor, you can first add NFS as Primary Storage (as you said) from there, later add Linstor and remove NFS.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] slavkap commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
slavkap commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r702874607



##########
File path: plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
##########
@@ -0,0 +1,720 @@
+// 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.driver;
+
+import com.linbit.linstor.api.ApiException;
+import com.linbit.linstor.api.CloneWaiter;
+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.ResourceDefinition;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted;
+import com.linbit.linstor.api.model.ResourceDefinitionCreate;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+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.VolumeDefinitionModify;
+
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.agent.api.to.DiskTO;
+import com.cloud.host.Host;
+import com.cloud.storage.ResizeVolumePayload;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.VolumeDetailVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.SnapshotDetailsDao;
+import com.cloud.storage.dao.SnapshotDetailsVO;
+import com.cloud.storage.dao.VMTemplatePoolDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.storage.command.CreateObjectAnswer;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
+import org.apache.cloudstack.storage.volume.VolumeObject;
+import org.apache.log4j.Logger;
+
+public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver {
+    private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class);
+    @Inject private PrimaryDataStoreDao _storagePoolDao;
+    @Inject private VolumeDao _volumeDao;
+    @Inject private VolumeDetailsDao _volumeDetailsDao;
+    @Inject private VMTemplatePoolDao _vmTemplatePoolDao;
+    @Inject private SnapshotDao _snapshotDao;
+    @Inject private SnapshotDetailsDao _snapshotDetailsDao;
+
+    public LinstorPrimaryDataStoreDriverImpl()
+    {
+    }
+
+    @Override
+    public Map<String, String> getCapabilities()
+    {
+        Map<String, String> mapCapabilities = new HashMap<>();
+
+        // Linstor will be restricted to only run on LVM-THIN and ZFS storage pools with ACS
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString(), Boolean.TRUE.toString());
+
+        // fetch if lvm-thin or ZFS
+        mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        // CAN_CREATE_VOLUME_FROM_SNAPSHOT see note from CAN_CREATE_VOLUME_FROM_VOLUME
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+        mapCapabilities.put(DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        return mapCapabilities;
+    }
+
+    @Override
+    public DataTO getTO(DataObject data)
+    {
+        return null;
+    }
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store)
+    {
+        return null;
+    }
+
+    @Override
+    public ChapInfo getChapInfo(DataObject dataObject)
+    {
+        return null;
+    }
+
+    @Override
+    public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+        return false;
+    }
+
+    @Override
+    public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+    }
+
+    @Override
+    public long getUsedBytes(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getUsedIops(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool)
+    {
+        return dataObject.getSize();
+    }
+
+    @Override
+    public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    private String getSnapshotName(String snapshotUuid) {
+        return LinstorUtil.RSC_PREFIX + snapshotUuid;
+    }
+
+    private void deleteResourceDefinition(StoragePoolVO storagePoolVO, String rscDefName)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceDefinitionDelete(rscDefName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete resource definition: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private void deleteSnapshot(@Nonnull DataStore dataStore, @Nonnull String rscDefName, @Nonnull String snapshotName)
+    {
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceSnapshotDelete(rscDefName, snapshotName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete snapshot: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private long getCsIdForCloning(long volumeId, String cloneOf) {
+        VolumeDetailVO volumeDetail = _volumeDetailsDao.findDetail(volumeId, cloneOf);
+
+        if (volumeDetail != null && volumeDetail.getValue() != null) {
+            return Long.parseLong(volumeDetail.getValue());
+        }
+
+        return Long.MIN_VALUE;
+    }
+
+    @Override
+    public void deleteAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("deleteAsync: " + dataObject.getType() + ";" + dataObject.getUuid());
+        String errMsg = null;
+
+        final long storagePoolId = dataStore.getId();
+        final StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
+
+        switch (dataObject.getType()) {
+            case VOLUME:
+            {
+                final VolumeInfo volumeInfo = (VolumeInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getPath();
+                deleteResourceDefinition(storagePool, rscName);
+
+                long usedBytes = storagePool.getUsedBytes();
+                long capacityIops = storagePool.getCapacityIops();
+
+                usedBytes -= volumeInfo.getSize();
+                if (volumeInfo.getMaxIops() != null)
+                    capacityIops += volumeInfo.getMaxIops();
+
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                storagePool.setCapacityIops(Math.max(0, capacityIops));
+
+                _storagePoolDao.update(storagePoolId, storagePool);
+            }
+                break;
+            case SNAPSHOT:
+                final SnapshotInfo snapshotInfo = (SnapshotInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + snapshotInfo.getBaseVolume().getPath();
+                deleteSnapshot(dataStore, rscName, getSnapshotName(snapshotInfo.getUuid()));
+                long usedBytes = storagePool.getUsedBytes() - snapshotInfo.getSize();
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                _storagePoolDao.update(storagePoolId, storagePool);
+                break;
+            default:
+                errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync";
+                s_logger.error(errMsg);
+        }
+
+        if (callback != null) {
+            CommandResult result = new CommandResult();
+            result.setResult(errMsg);
+
+            callback.complete(result);
+        }
+    }
+
+    private void logLinstorAnswer(@Nonnull ApiCallRc answer) {
+        if (answer.isError()) {
+            s_logger.error(answer.getMessage());
+        } else if (answer.isWarning()) {
+            s_logger.warn(answer.getMessage());
+        } else if (answer.isInfo()) {
+            s_logger.info(answer.getMessage());
+        }
+    }
+
+    private void logLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        answers.forEach(this::logLinstorAnswer);
+    }
+
+    private void checkLinstorAnswersThrow(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        if (answers.hasError())
+        {
+            String errMsg = answers.stream()
+                .filter(ApiCallRc::isError)
+                .findFirst()
+                .map(ApiCallRc::getMessage).orElse("Unknown linstor error");
+            throw new CloudRuntimeException(errMsg);
+        }
+    }
+
+    private String checkLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        return answers.stream().filter(ApiCallRc::isError).findFirst().map(ApiCallRc::getMessage).orElse(null);
+    }
+
+    private String getDeviceName(DevelopersApi linstorApi, String rscName) throws ApiException {
+        List<ResourceWithVolumes> resources = linstorApi.viewResources(
+            Collections.emptyList(),
+            Collections.singletonList(rscName),
+            Collections.emptyList(),
+            null,
+            null,
+            null);
+        if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty())
+        {
+            s_logger.info("Linstor: Created drbd device: " + resources.get(0).getVolumes().get(0).getDevicePath());
+            return resources.get(0).getVolumes().get(0).getDevicePath();
+        } else
+        {
+            s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+            throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+        }
+    }
+
+    private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+
+        ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn();
+        final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
+        rscGrpSpawn.setResourceDefinitionName(rscName);
+        rscGrpSpawn.addVolumeSizesItem(vol.getSize() / 1024);
+
+        try
+        {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = linstorApi.resourceGroupSpawn(rscGrp, rscGrpSpawn);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String cloneResource(long csCloneId, VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        // get the cached template on this storage
+        VMTemplateStoragePoolVO tmplPoolRef = _vmTemplatePoolDao.findByPoolTemplate(
+            storagePoolVO.getId(), csCloneId, null);
+
+        if (tmplPoolRef != null) {
+            final String cloneRes = LinstorUtil.RSC_PREFIX + tmplPoolRef.getLocalDownloadPath();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+            try {
+                s_logger.debug("Clone resource definition " + cloneRes + " to " + rscName);
+                ResourceDefinitionCloneRequest cloneRequest = new ResourceDefinitionCloneRequest();
+                cloneRequest.setName(rscName);
+                ResourceDefinitionCloneStarted cloneStarted = linstorApi.resourceDefinitionClone(
+                    cloneRes, cloneRequest);
+
+                checkLinstorAnswersThrow(cloneStarted.getMessages());
+
+                if (!CloneWaiter.waitFor(linstorApi, cloneStarted)) {
+                    throw new CloudRuntimeException("Clone for resource " + rscName + " failed.");
+                }
+
+                return getDeviceName(linstorApi, rscName);
+            } catch (ApiException apiEx) {
+                s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+                throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+            }
+        } else {
+            throw new CloudRuntimeException(
+                "Unable to find Linstor resource for the following template data-object ID: " + csCloneId);
+        }
+    }
+
+    private String createResourceFromSnapshot(long csSnapshotId, String rscName, StoragePoolVO storagePoolVO) {
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+        final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        SnapshotVO snapshotVO = _snapshotDao.findById(csSnapshotId);
+        String snapName = LinstorUtil.RSC_PREFIX + snapshotVO.getUuid();
+        VolumeVO volumeVO = _volumeDao.findById(snapshotVO.getVolumeId());
+        String cloneRes = LinstorUtil.RSC_PREFIX + volumeVO.getPath();
+
+        try
+        {
+            s_logger.debug("Create new resource definition: " + rscName);
+            ResourceDefinitionCreate rdCreate = new ResourceDefinitionCreate();
+            ResourceDefinition rd = new ResourceDefinition();
+            rd.setName(rscName);
+            rd.setResourceGroupName(rscGrp);
+            rdCreate.setResourceDefinition(rd);
+            ApiCallRcList answers = linstorApi.resourceDefinitionCreate(rdCreate);
+            checkLinstorAnswersThrow(answers);
+
+            SnapshotRestore snapshotRestore = new SnapshotRestore();
+            snapshotRestore.toResource(rscName);
+
+            s_logger.debug("Create new volume definition for snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotsRestoreVolumeDefinition(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            // restore snapshot to new resource
+            s_logger.debug("Restore resource from snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotRestore(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String createVolume(VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = getCsIdForCloning(volumeInfo.getId(), "cloneOfSnapshot");
+        long csTemplateId = getCsIdForCloning(volumeInfo.getId(), "cloneOfTemplate");
+
+        if (csSnapshotId > 0) {
+            return createResourceFromSnapshot(csSnapshotId, LinstorUtil.RSC_PREFIX + volumeInfo.getUuid(), storagePoolVO);
+        } else if (csTemplateId > 0) {
+            return cloneResource(csTemplateId, volumeInfo, storagePoolVO);
+        } else {
+            return createResource(volumeInfo, storagePoolVO);
+        }
+    }
+
+    private void handleSnapshotDetails(long csSnapshotId, String name, String value) {
+        _snapshotDetailsDao.removeDetail(csSnapshotId, name);
+        SnapshotDetailsVO snapshotDetails = new SnapshotDetailsVO(csSnapshotId, name, value, false);
+        _snapshotDetailsDao.persist(snapshotDetails);
+    }
+
+    private void addTempVolumeToDb(long csSnapshotId, String tempVolumeName) {
+        // TEMP_VOLUME_ID is needed, to find which temporary resource should be deleted after copying it on agent side
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, LinstorUtil.RSC_PREFIX + tempVolumeName);
+        // the iqn will be used on the agent side to copy from, even though linstor doesn't have anything to do with IQN
+        handleSnapshotDetails(csSnapshotId, DiskTO.IQN, tempVolumeName);
+    }
+
+    private void removeTempVolumeFromDb(long csSnapshotId) {
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+        if (snapshotDetails == null || snapshotDetails.getValue() == null) {
+            throw new CloudRuntimeException(
+                "'removeTempVolumeId' should not be invoked unless " + LinstorUtil.TEMP_VOLUME_ID + " exists.");
+        }
+
+        String originalVolumeId = snapshotDetails.getValue();
+
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, originalVolumeId);
+
+        _snapshotDetailsDao.remove(snapshotDetails.getId());
+    }
+
+    private void createVolumeFromSnapshot(SnapshotInfo snapshotInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = snapshotInfo.getId();
+
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, "tempVolume");
+
+        if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("create"))
+        {
+            final String csName = "Temp-" + snapshotInfo.getUuid();
+            final String tempRscName = LinstorUtil.RSC_PREFIX + csName;
+            createResourceFromSnapshot(csSnapshotId, tempRscName, storagePoolVO);
+
+            s_logger.debug("Temp resource created: " + tempRscName);
+            addTempVolumeToDb(csSnapshotId, csName);
+        }
+        else if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("delete"))
+        {
+            snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+            deleteResourceDefinition(storagePoolVO, snapshotDetails.getValue());
+
+            s_logger.debug("Temp resource deleted: " + snapshotDetails.getValue());
+            removeTempVolumeFromDb(csSnapshotId);
+        }
+        else {
+            throw new CloudRuntimeException("Invalid state in 'createVolumeFromSnapshot(SnapshotInfo, StoragePoolVO)'");
+        }
+    }
+
+    @Override
+    public void createAsync(DataStore dataStore, DataObject vol, AsyncCompletionCallback<CreateCmdResult> callback)
+    {
+        String devPath = null;
+        String errMsg = null;
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+
+        try
+        {
+            switch (vol.getType())
+            {
+                case VOLUME:
+                    VolumeInfo volumeInfo = (VolumeInfo) vol;
+                    VolumeVO volume = _volumeDao.findById(volumeInfo.getId());
+                    s_logger.debug("createAsync - creating volume");
+                    devPath = createVolume(volumeInfo, storagePool);
+                    volume.setFolder("/dev/");
+                    volume.setPoolId(storagePool.getId());
+                    volume.setUuid(vol.getUuid());
+                    volume.setPath(vol.getUuid());
+
+                    _volumeDao.update(volume.getId(), volume);
+                    break;
+                case SNAPSHOT:
+                    s_logger.debug("createAsync - SNAPSHOT");
+                    createVolumeFromSnapshot((SnapshotInfo) vol, storagePool);
+                    break;
+                case TEMPLATE:
+                    errMsg = "creating template - not supported";
+                    s_logger.error("createAsync - " + errMsg);
+                    break;
+                default:
+                    errMsg = "Invalid DataObjectType (" + vol.getType() + ") passed to createAsync";
+                    s_logger.error(errMsg);
+            }
+        } catch (Exception ex)
+        {
+            errMsg = ex.getMessage();
+
+            s_logger.error("createAsync: " + errMsg);
+            if (callback == null)
+            {
+                throw ex;
+            }
+        }
+
+        if (callback != null)
+        {
+            CreateCmdResult result = new CreateCmdResult(devPath, new Answer(null, errMsg == null, errMsg));
+            result.setResult(errMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public void revertSnapshot(
+        SnapshotInfo snapshot,
+        SnapshotInfo snapshotOnPrimaryStore,
+        AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("Linstor: revertSnapshot");
+        final VolumeInfo volumeInfo = snapshot.getBaseVolume();
+        VolumeVO volumeVO = _volumeDao.findById(volumeInfo.getId());
+        if (volumeVO == null || volumeVO.getRemoved() != null) {
+            CommandResult commandResult = new CommandResult();
+            commandResult.setResult("The volume that the snapshot belongs to no longer exists.");
+            callback.complete(commandResult);
+            return;
+        }
+
+        String resultMsg;
+        try {
+            final StoragePool pool = (StoragePool) snapshot.getDataStore();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final String snapName = LinstorUtil.RSC_PREFIX + snapshot.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(pool.getHostAddress());
+
+            ApiCallRcList answers = linstorApi.resourceSnapshotRollback(rscName, snapName);
+            resultMsg = checkLinstorAnswers(answers);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            resultMsg = apiEx.getBestMessage();
+        }
+
+        if (callback != null)
+        {
+            CommandResult result = new CommandResult();
+            result.setResult(resultMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public boolean canCopy(DataObject srcData, DataObject destData)
+    {
+        return false;
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback)

Review comment:
       @andrijapanicsb, we at StorPool support live resize. It is supported for NFS/Ceph also




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-917060748


   @nvazquez a Jenkins job has been kicked to build packages. I'll keep you posted as I make progress.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-909251216


   @blueorangutan test


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-919103545


   @rhtyd a Jenkins job has been kicked to build packages. I'll keep you posted as I make progress.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-870563804


   @rhtyd This is now ready for testing/review, except for one create path, because I don't know how to trigger it.
   And that is the `createAsync` with a volume type of `TEMPLATE`. If you could guide me how I could trigger that (or maybe it isn't even necessary with our set capabilities).
   
   The rest of the functionality should work as long as no LVM-Thick storagepool is used by linstor, as die plugin now requires linstor storage pools that support snapshots.
   Also the included (copied solidfire) integration-test works on my setup.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-879615887


   <b>Trillian test result (tid-1265)</b>
   Environment: kvm-centos7 (x2), Advanced Networking with Mgmt server 7
   Total time taken: 55065 seconds
   Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr4994-t1265-kvm-centos7.zip
   Intermittent failure detected: /marvin/tests/smoke/test_kubernetes_clusters.py
   Intermittent failure detected: /marvin/tests/smoke/test_network.py
   Intermittent failure detected: /marvin/tests/smoke/test_privategw_acl.py
   Intermittent failure detected: /marvin/tests/smoke/test_routers_network_ops.py
   Intermittent failure detected: /marvin/tests/smoke/test_vpc_redundant.py
   Smoke tests completed. 85 look OK, 3 have error(s)
   Only failed tests results shown below:
   
   
   Test | Result | Time (s) | Test File
   --- | --- | --- | ---
   test_01_invalid_upgrade_kubernetes_cluster | `Failure` | 3615.27 | test_kubernetes_clusters.py
   test_02_deploy_and_upgrade_kubernetes_cluster | `Failure` | 3615.37 | test_kubernetes_clusters.py
   test_03_deploy_and_scale_kubernetes_cluster | `Failure` | 0.05 | test_kubernetes_clusters.py
   test_04_basic_lifecycle_kubernetes_cluster | `Failure` | 0.04 | test_kubernetes_clusters.py
   test_05_delete_kubernetes_cluster | `Failure` | 0.04 | test_kubernetes_clusters.py
   test_07_deploy_kubernetes_ha_cluster | `Failure` | 0.04 | test_kubernetes_clusters.py
   test_08_deploy_and_upgrade_kubernetes_ha_cluster | `Failure` | 0.04 | test_kubernetes_clusters.py
   test_09_delete_kubernetes_ha_cluster | `Failure` | 0.04 | test_kubernetes_clusters.py
   ContextSuite context=TestKubernetesCluster>:teardown | `Error` | 103.46 | test_kubernetes_clusters.py
   test_02_RVR_Network_FW_PF_SSH_default_routes_egress_false | `Failure` | 339.72 | test_routers_network_ops.py
   test_01_create_redundant_VPC_2tiers_4VMs_4IPs_4PF_ACL | `Failure` | 535.35 | test_vpc_redundant.py
   test_03_create_redundant_VPC_1tier_2VMs_2IPs_2PF_ACL_reboot_routers | `Failure` | 463.85 | test_vpc_redundant.py
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-879849873


   @rhtyd 
   > For example, you can run and share results of your smoketests in your env (or using virtualised https://github.com/shapeblue/mbx) with Linstor storage with KVM as the only primary storage. Or a set of manual tests. We can merge based on your provided test results and regression smoketests, and after all review items are addressed.
   
   So I'm in the middle of setup a new cluster with just Linstor as primary storage, and in the setup wizard I noticed that only the options for (nfs, SharedMountPoint, RBD, CLVM, Gluster). So why are here solidfire, datera and others missing and is it ok to add the default nfs primary storage and afteward deleted it and add just a Linstor one? Or should I be able to already pick Linstor here?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] slavkap commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
slavkap commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r702873843



##########
File path: plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
##########
@@ -0,0 +1,720 @@
+// 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.driver;
+
+import com.linbit.linstor.api.ApiException;
+import com.linbit.linstor.api.CloneWaiter;
+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.ResourceDefinition;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted;
+import com.linbit.linstor.api.model.ResourceDefinitionCreate;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+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.VolumeDefinitionModify;
+
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.agent.api.to.DiskTO;
+import com.cloud.host.Host;
+import com.cloud.storage.ResizeVolumePayload;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.VolumeDetailVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.SnapshotDetailsDao;
+import com.cloud.storage.dao.SnapshotDetailsVO;
+import com.cloud.storage.dao.VMTemplatePoolDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.storage.command.CreateObjectAnswer;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
+import org.apache.cloudstack.storage.volume.VolumeObject;
+import org.apache.log4j.Logger;
+
+public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver {
+    private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class);
+    @Inject private PrimaryDataStoreDao _storagePoolDao;
+    @Inject private VolumeDao _volumeDao;
+    @Inject private VolumeDetailsDao _volumeDetailsDao;
+    @Inject private VMTemplatePoolDao _vmTemplatePoolDao;
+    @Inject private SnapshotDao _snapshotDao;
+    @Inject private SnapshotDetailsDao _snapshotDetailsDao;
+
+    public LinstorPrimaryDataStoreDriverImpl()
+    {
+    }
+
+    @Override
+    public Map<String, String> getCapabilities()
+    {
+        Map<String, String> mapCapabilities = new HashMap<>();
+
+        // Linstor will be restricted to only run on LVM-THIN and ZFS storage pools with ACS
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString(), Boolean.TRUE.toString());
+
+        // fetch if lvm-thin or ZFS
+        mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        // CAN_CREATE_VOLUME_FROM_SNAPSHOT see note from CAN_CREATE_VOLUME_FROM_VOLUME
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+        mapCapabilities.put(DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        return mapCapabilities;
+    }
+
+    @Override
+    public DataTO getTO(DataObject data)
+    {
+        return null;
+    }
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store)
+    {
+        return null;
+    }
+
+    @Override
+    public ChapInfo getChapInfo(DataObject dataObject)
+    {
+        return null;
+    }
+
+    @Override
+    public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+        return false;
+    }
+
+    @Override
+    public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+    }
+
+    @Override
+    public long getUsedBytes(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getUsedIops(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool)
+    {
+        return dataObject.getSize();
+    }
+
+    @Override
+    public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    private String getSnapshotName(String snapshotUuid) {
+        return LinstorUtil.RSC_PREFIX + snapshotUuid;
+    }
+
+    private void deleteResourceDefinition(StoragePoolVO storagePoolVO, String rscDefName)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceDefinitionDelete(rscDefName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete resource definition: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private void deleteSnapshot(@Nonnull DataStore dataStore, @Nonnull String rscDefName, @Nonnull String snapshotName)
+    {
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceSnapshotDelete(rscDefName, snapshotName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete snapshot: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private long getCsIdForCloning(long volumeId, String cloneOf) {
+        VolumeDetailVO volumeDetail = _volumeDetailsDao.findDetail(volumeId, cloneOf);
+
+        if (volumeDetail != null && volumeDetail.getValue() != null) {
+            return Long.parseLong(volumeDetail.getValue());
+        }
+
+        return Long.MIN_VALUE;
+    }
+
+    @Override
+    public void deleteAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("deleteAsync: " + dataObject.getType() + ";" + dataObject.getUuid());
+        String errMsg = null;
+
+        final long storagePoolId = dataStore.getId();
+        final StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
+
+        switch (dataObject.getType()) {
+            case VOLUME:
+            {
+                final VolumeInfo volumeInfo = (VolumeInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getPath();
+                deleteResourceDefinition(storagePool, rscName);
+
+                long usedBytes = storagePool.getUsedBytes();
+                long capacityIops = storagePool.getCapacityIops();
+
+                usedBytes -= volumeInfo.getSize();
+                if (volumeInfo.getMaxIops() != null)
+                    capacityIops += volumeInfo.getMaxIops();
+
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                storagePool.setCapacityIops(Math.max(0, capacityIops));
+
+                _storagePoolDao.update(storagePoolId, storagePool);
+            }
+                break;
+            case SNAPSHOT:
+                final SnapshotInfo snapshotInfo = (SnapshotInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + snapshotInfo.getBaseVolume().getPath();
+                deleteSnapshot(dataStore, rscName, getSnapshotName(snapshotInfo.getUuid()));
+                long usedBytes = storagePool.getUsedBytes() - snapshotInfo.getSize();
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                _storagePoolDao.update(storagePoolId, storagePool);
+                break;
+            default:
+                errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync";
+                s_logger.error(errMsg);
+        }
+
+        if (callback != null) {
+            CommandResult result = new CommandResult();
+            result.setResult(errMsg);
+
+            callback.complete(result);
+        }
+    }
+
+    private void logLinstorAnswer(@Nonnull ApiCallRc answer) {
+        if (answer.isError()) {
+            s_logger.error(answer.getMessage());
+        } else if (answer.isWarning()) {
+            s_logger.warn(answer.getMessage());
+        } else if (answer.isInfo()) {
+            s_logger.info(answer.getMessage());
+        }
+    }
+
+    private void logLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        answers.forEach(this::logLinstorAnswer);
+    }
+
+    private void checkLinstorAnswersThrow(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        if (answers.hasError())
+        {
+            String errMsg = answers.stream()
+                .filter(ApiCallRc::isError)
+                .findFirst()
+                .map(ApiCallRc::getMessage).orElse("Unknown linstor error");
+            throw new CloudRuntimeException(errMsg);
+        }
+    }
+
+    private String checkLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        return answers.stream().filter(ApiCallRc::isError).findFirst().map(ApiCallRc::getMessage).orElse(null);
+    }
+
+    private String getDeviceName(DevelopersApi linstorApi, String rscName) throws ApiException {
+        List<ResourceWithVolumes> resources = linstorApi.viewResources(
+            Collections.emptyList(),
+            Collections.singletonList(rscName),
+            Collections.emptyList(),
+            null,
+            null,
+            null);
+        if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty())
+        {
+            s_logger.info("Linstor: Created drbd device: " + resources.get(0).getVolumes().get(0).getDevicePath());
+            return resources.get(0).getVolumes().get(0).getDevicePath();
+        } else
+        {
+            s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+            throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+        }
+    }
+
+    private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+
+        ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn();
+        final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
+        rscGrpSpawn.setResourceDefinitionName(rscName);
+        rscGrpSpawn.addVolumeSizesItem(vol.getSize() / 1024);
+
+        try
+        {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = linstorApi.resourceGroupSpawn(rscGrp, rscGrpSpawn);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String cloneResource(long csCloneId, VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        // get the cached template on this storage
+        VMTemplateStoragePoolVO tmplPoolRef = _vmTemplatePoolDao.findByPoolTemplate(
+            storagePoolVO.getId(), csCloneId, null);
+
+        if (tmplPoolRef != null) {
+            final String cloneRes = LinstorUtil.RSC_PREFIX + tmplPoolRef.getLocalDownloadPath();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+            try {
+                s_logger.debug("Clone resource definition " + cloneRes + " to " + rscName);
+                ResourceDefinitionCloneRequest cloneRequest = new ResourceDefinitionCloneRequest();
+                cloneRequest.setName(rscName);
+                ResourceDefinitionCloneStarted cloneStarted = linstorApi.resourceDefinitionClone(
+                    cloneRes, cloneRequest);
+
+                checkLinstorAnswersThrow(cloneStarted.getMessages());
+
+                if (!CloneWaiter.waitFor(linstorApi, cloneStarted)) {
+                    throw new CloudRuntimeException("Clone for resource " + rscName + " failed.");
+                }
+
+                return getDeviceName(linstorApi, rscName);
+            } catch (ApiException apiEx) {
+                s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+                throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+            }
+        } else {
+            throw new CloudRuntimeException(
+                "Unable to find Linstor resource for the following template data-object ID: " + csCloneId);
+        }
+    }
+
+    private String createResourceFromSnapshot(long csSnapshotId, String rscName, StoragePoolVO storagePoolVO) {
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+        final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        SnapshotVO snapshotVO = _snapshotDao.findById(csSnapshotId);
+        String snapName = LinstorUtil.RSC_PREFIX + snapshotVO.getUuid();
+        VolumeVO volumeVO = _volumeDao.findById(snapshotVO.getVolumeId());
+        String cloneRes = LinstorUtil.RSC_PREFIX + volumeVO.getPath();
+
+        try
+        {
+            s_logger.debug("Create new resource definition: " + rscName);
+            ResourceDefinitionCreate rdCreate = new ResourceDefinitionCreate();
+            ResourceDefinition rd = new ResourceDefinition();
+            rd.setName(rscName);
+            rd.setResourceGroupName(rscGrp);
+            rdCreate.setResourceDefinition(rd);
+            ApiCallRcList answers = linstorApi.resourceDefinitionCreate(rdCreate);
+            checkLinstorAnswersThrow(answers);
+
+            SnapshotRestore snapshotRestore = new SnapshotRestore();
+            snapshotRestore.toResource(rscName);
+
+            s_logger.debug("Create new volume definition for snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotsRestoreVolumeDefinition(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            // restore snapshot to new resource
+            s_logger.debug("Restore resource from snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotRestore(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String createVolume(VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = getCsIdForCloning(volumeInfo.getId(), "cloneOfSnapshot");
+        long csTemplateId = getCsIdForCloning(volumeInfo.getId(), "cloneOfTemplate");
+
+        if (csSnapshotId > 0) {
+            return createResourceFromSnapshot(csSnapshotId, LinstorUtil.RSC_PREFIX + volumeInfo.getUuid(), storagePoolVO);
+        } else if (csTemplateId > 0) {
+            return cloneResource(csTemplateId, volumeInfo, storagePoolVO);
+        } else {
+            return createResource(volumeInfo, storagePoolVO);
+        }
+    }
+
+    private void handleSnapshotDetails(long csSnapshotId, String name, String value) {
+        _snapshotDetailsDao.removeDetail(csSnapshotId, name);
+        SnapshotDetailsVO snapshotDetails = new SnapshotDetailsVO(csSnapshotId, name, value, false);
+        _snapshotDetailsDao.persist(snapshotDetails);
+    }
+
+    private void addTempVolumeToDb(long csSnapshotId, String tempVolumeName) {
+        // TEMP_VOLUME_ID is needed, to find which temporary resource should be deleted after copying it on agent side
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, LinstorUtil.RSC_PREFIX + tempVolumeName);
+        // the iqn will be used on the agent side to copy from, even though linstor doesn't have anything to do with IQN
+        handleSnapshotDetails(csSnapshotId, DiskTO.IQN, tempVolumeName);
+    }
+
+    private void removeTempVolumeFromDb(long csSnapshotId) {
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+        if (snapshotDetails == null || snapshotDetails.getValue() == null) {
+            throw new CloudRuntimeException(
+                "'removeTempVolumeId' should not be invoked unless " + LinstorUtil.TEMP_VOLUME_ID + " exists.");
+        }
+
+        String originalVolumeId = snapshotDetails.getValue();
+
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, originalVolumeId);
+
+        _snapshotDetailsDao.remove(snapshotDetails.getId());
+    }
+
+    private void createVolumeFromSnapshot(SnapshotInfo snapshotInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = snapshotInfo.getId();
+
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, "tempVolume");
+
+        if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("create"))
+        {
+            final String csName = "Temp-" + snapshotInfo.getUuid();
+            final String tempRscName = LinstorUtil.RSC_PREFIX + csName;
+            createResourceFromSnapshot(csSnapshotId, tempRscName, storagePoolVO);
+
+            s_logger.debug("Temp resource created: " + tempRscName);
+            addTempVolumeToDb(csSnapshotId, csName);
+        }
+        else if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("delete"))
+        {
+            snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+            deleteResourceDefinition(storagePoolVO, snapshotDetails.getValue());
+
+            s_logger.debug("Temp resource deleted: " + snapshotDetails.getValue());
+            removeTempVolumeFromDb(csSnapshotId);
+        }
+        else {
+            throw new CloudRuntimeException("Invalid state in 'createVolumeFromSnapshot(SnapshotInfo, StoragePoolVO)'");
+        }
+    }
+
+    @Override
+    public void createAsync(DataStore dataStore, DataObject vol, AsyncCompletionCallback<CreateCmdResult> callback)
+    {
+        String devPath = null;
+        String errMsg = null;
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+
+        try
+        {
+            switch (vol.getType())
+            {
+                case VOLUME:
+                    VolumeInfo volumeInfo = (VolumeInfo) vol;
+                    VolumeVO volume = _volumeDao.findById(volumeInfo.getId());
+                    s_logger.debug("createAsync - creating volume");
+                    devPath = createVolume(volumeInfo, storagePool);
+                    volume.setFolder("/dev/");
+                    volume.setPoolId(storagePool.getId());
+                    volume.setUuid(vol.getUuid());
+                    volume.setPath(vol.getUuid());
+
+                    _volumeDao.update(volume.getId(), volume);
+                    break;
+                case SNAPSHOT:
+                    s_logger.debug("createAsync - SNAPSHOT");
+                    createVolumeFromSnapshot((SnapshotInfo) vol, storagePool);
+                    break;
+                case TEMPLATE:
+                    errMsg = "creating template - not supported";
+                    s_logger.error("createAsync - " + errMsg);
+                    break;
+                default:
+                    errMsg = "Invalid DataObjectType (" + vol.getType() + ") passed to createAsync";
+                    s_logger.error(errMsg);
+            }
+        } catch (Exception ex)
+        {
+            errMsg = ex.getMessage();
+
+            s_logger.error("createAsync: " + errMsg);
+            if (callback == null)
+            {
+                throw ex;
+            }
+        }
+
+        if (callback != null)
+        {
+            CreateCmdResult result = new CreateCmdResult(devPath, new Answer(null, errMsg == null, errMsg));
+            result.setResult(errMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public void revertSnapshot(
+        SnapshotInfo snapshot,
+        SnapshotInfo snapshotOnPrimaryStore,
+        AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("Linstor: revertSnapshot");
+        final VolumeInfo volumeInfo = snapshot.getBaseVolume();
+        VolumeVO volumeVO = _volumeDao.findById(volumeInfo.getId());
+        if (volumeVO == null || volumeVO.getRemoved() != null) {
+            CommandResult commandResult = new CommandResult();
+            commandResult.setResult("The volume that the snapshot belongs to no longer exists.");
+            callback.complete(commandResult);
+            return;
+        }
+
+        String resultMsg;
+        try {
+            final StoragePool pool = (StoragePool) snapshot.getDataStore();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final String snapName = LinstorUtil.RSC_PREFIX + snapshot.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(pool.getHostAddress());
+
+            ApiCallRcList answers = linstorApi.resourceSnapshotRollback(rscName, snapName);
+            resultMsg = checkLinstorAnswers(answers);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            resultMsg = apiEx.getBestMessage();
+        }
+
+        if (callback != null)
+        {
+            CommandResult result = new CommandResult();
+            result.setResult(resultMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public boolean canCopy(DataObject srcData, DataObject destData)
+    {
+        return false;
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback)

Review comment:
       @rp-, most of the plugins in CloudStack are for managed storage. In your case I think it will be useful for you to check the default driver implementation - `CloudStackPrimaryDataStoreDriverImpl.resize()` and maybe you have to modify `LibvirtResizeVolumeCommandWrapper` or to add a custom `CommandWrapper`.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] nvazquez commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
nvazquez commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-917060158


   @blueorangutan package


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-919102706


   @blueorangutan package


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-920331193


   <b>Trillian test result (tid-2058)</b>
   Environment: vmware-67u3 (x2), Advanced Networking with Mgmt server 7
   Total time taken: 41973 seconds
   Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr4994-t2058-vmware-67u3.zip
   Smoke tests completed. 89 look OK, 0 have errors
   Only failed tests results shown below:
   
   
   Test | Result | Time (s) | Test File
   --- | --- | --- | ---
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-870349841






-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rhtyd commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rhtyd commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-956571240


   @rp- @Philipp-Reisner can you check a potential Linstor regression - https://markmail.org/message/tc4qkm2izzcjqahn 
   I'll tag/cc you in the email thread too.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] blueorangutan commented on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
blueorangutan commented on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-914343452


   @nvazquez a Jenkins job has been kicked to build packages. I'll keep you posted as I make progress.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] rp- commented on a change in pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
rp- commented on a change in pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#discussion_r704428429



##########
File path: plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java
##########
@@ -0,0 +1,720 @@
+// 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.driver;
+
+import com.linbit.linstor.api.ApiException;
+import com.linbit.linstor.api.CloneWaiter;
+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.ResourceDefinition;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneRequest;
+import com.linbit.linstor.api.model.ResourceDefinitionCloneStarted;
+import com.linbit.linstor.api.model.ResourceDefinitionCreate;
+import com.linbit.linstor.api.model.ResourceGroupSpawn;
+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.VolumeDefinitionModify;
+
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.DataTO;
+import com.cloud.agent.api.to.DiskTO;
+import com.cloud.host.Host;
+import com.cloud.storage.ResizeVolumePayload;
+import com.cloud.storage.SnapshotVO;
+import com.cloud.storage.StoragePool;
+import com.cloud.storage.VMTemplateStoragePoolVO;
+import com.cloud.storage.VolumeDetailVO;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.SnapshotDao;
+import com.cloud.storage.dao.SnapshotDetailsDao;
+import com.cloud.storage.dao.SnapshotDetailsVO;
+import com.cloud.storage.dao.VMTemplatePoolDao;
+import com.cloud.storage.dao.VolumeDao;
+import com.cloud.storage.dao.VolumeDetailsDao;
+import com.cloud.utils.Pair;
+import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataObject;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
+import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver;
+import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
+import org.apache.cloudstack.framework.async.AsyncCompletionCallback;
+import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.storage.command.CreateObjectAnswer;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
+import org.apache.cloudstack.storage.datastore.util.LinstorUtil;
+import org.apache.cloudstack.storage.to.SnapshotObjectTO;
+import org.apache.cloudstack.storage.volume.VolumeObject;
+import org.apache.log4j.Logger;
+
+public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver {
+    private static final Logger s_logger = Logger.getLogger(LinstorPrimaryDataStoreDriverImpl.class);
+    @Inject private PrimaryDataStoreDao _storagePoolDao;
+    @Inject private VolumeDao _volumeDao;
+    @Inject private VolumeDetailsDao _volumeDetailsDao;
+    @Inject private VMTemplatePoolDao _vmTemplatePoolDao;
+    @Inject private SnapshotDao _snapshotDao;
+    @Inject private SnapshotDetailsDao _snapshotDetailsDao;
+
+    public LinstorPrimaryDataStoreDriverImpl()
+    {
+    }
+
+    @Override
+    public Map<String, String> getCapabilities()
+    {
+        Map<String, String> mapCapabilities = new HashMap<>();
+
+        // Linstor will be restricted to only run on LVM-THIN and ZFS storage pools with ACS
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_VOLUME.toString(), Boolean.TRUE.toString());
+
+        // fetch if lvm-thin or ZFS
+        mapCapabilities.put(DataStoreCapabilities.STORAGE_SYSTEM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        // CAN_CREATE_VOLUME_FROM_SNAPSHOT see note from CAN_CREATE_VOLUME_FROM_VOLUME
+        mapCapabilities.put(DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT.toString(), Boolean.TRUE.toString());
+        mapCapabilities.put(DataStoreCapabilities.CAN_REVERT_VOLUME_TO_SNAPSHOT.toString(), Boolean.TRUE.toString());
+
+        return mapCapabilities;
+    }
+
+    @Override
+    public DataTO getTO(DataObject data)
+    {
+        return null;
+    }
+
+    @Override
+    public DataStoreTO getStoreTO(DataStore store)
+    {
+        return null;
+    }
+
+    @Override
+    public ChapInfo getChapInfo(DataObject dataObject)
+    {
+        return null;
+    }
+
+    @Override
+    public boolean grantAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+        return false;
+    }
+
+    @Override
+    public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore)
+    {
+    }
+
+    @Override
+    public long getUsedBytes(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getUsedIops(StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    @Override
+    public long getDataObjectSizeIncludingHypervisorSnapshotReserve(DataObject dataObject, StoragePool pool)
+    {
+        return dataObject.getSize();
+    }
+
+    @Override
+    public long getBytesRequiredForTemplate(TemplateInfo templateInfo, StoragePool storagePool)
+    {
+        return 0;
+    }
+
+    private String getSnapshotName(String snapshotUuid) {
+        return LinstorUtil.RSC_PREFIX + snapshotUuid;
+    }
+
+    private void deleteResourceDefinition(StoragePoolVO storagePoolVO, String rscDefName)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceDefinitionDelete(rscDefName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete resource definition: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private void deleteSnapshot(@Nonnull DataStore dataStore, @Nonnull String rscDefName, @Nonnull String snapshotName)
+    {
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePool.getHostAddress());
+
+        try
+        {
+            ApiCallRcList answers = linstorApi.resourceSnapshotDelete(rscDefName, snapshotName);
+            if (answers.hasError())
+            {
+                for (ApiCallRc answer : answers)
+                {
+                    s_logger.error(answer.getMessage());
+                }
+                throw new CloudRuntimeException("Linstor: Unable to delete snapshot: " + rscDefName);
+            }
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private long getCsIdForCloning(long volumeId, String cloneOf) {
+        VolumeDetailVO volumeDetail = _volumeDetailsDao.findDetail(volumeId, cloneOf);
+
+        if (volumeDetail != null && volumeDetail.getValue() != null) {
+            return Long.parseLong(volumeDetail.getValue());
+        }
+
+        return Long.MIN_VALUE;
+    }
+
+    @Override
+    public void deleteAsync(DataStore dataStore, DataObject dataObject, AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("deleteAsync: " + dataObject.getType() + ";" + dataObject.getUuid());
+        String errMsg = null;
+
+        final long storagePoolId = dataStore.getId();
+        final StoragePoolVO storagePool = _storagePoolDao.findById(storagePoolId);
+
+        switch (dataObject.getType()) {
+            case VOLUME:
+            {
+                final VolumeInfo volumeInfo = (VolumeInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getPath();
+                deleteResourceDefinition(storagePool, rscName);
+
+                long usedBytes = storagePool.getUsedBytes();
+                long capacityIops = storagePool.getCapacityIops();
+
+                usedBytes -= volumeInfo.getSize();
+                if (volumeInfo.getMaxIops() != null)
+                    capacityIops += volumeInfo.getMaxIops();
+
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                storagePool.setCapacityIops(Math.max(0, capacityIops));
+
+                _storagePoolDao.update(storagePoolId, storagePool);
+            }
+                break;
+            case SNAPSHOT:
+                final SnapshotInfo snapshotInfo = (SnapshotInfo) dataObject;
+                final String rscName = LinstorUtil.RSC_PREFIX + snapshotInfo.getBaseVolume().getPath();
+                deleteSnapshot(dataStore, rscName, getSnapshotName(snapshotInfo.getUuid()));
+                long usedBytes = storagePool.getUsedBytes() - snapshotInfo.getSize();
+                storagePool.setUsedBytes(Math.max(0, usedBytes));
+                _storagePoolDao.update(storagePoolId, storagePool);
+                break;
+            default:
+                errMsg = "Invalid DataObjectType (" + dataObject.getType() + ") passed to deleteAsync";
+                s_logger.error(errMsg);
+        }
+
+        if (callback != null) {
+            CommandResult result = new CommandResult();
+            result.setResult(errMsg);
+
+            callback.complete(result);
+        }
+    }
+
+    private void logLinstorAnswer(@Nonnull ApiCallRc answer) {
+        if (answer.isError()) {
+            s_logger.error(answer.getMessage());
+        } else if (answer.isWarning()) {
+            s_logger.warn(answer.getMessage());
+        } else if (answer.isInfo()) {
+            s_logger.info(answer.getMessage());
+        }
+    }
+
+    private void logLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        answers.forEach(this::logLinstorAnswer);
+    }
+
+    private void checkLinstorAnswersThrow(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        if (answers.hasError())
+        {
+            String errMsg = answers.stream()
+                .filter(ApiCallRc::isError)
+                .findFirst()
+                .map(ApiCallRc::getMessage).orElse("Unknown linstor error");
+            throw new CloudRuntimeException(errMsg);
+        }
+    }
+
+    private String checkLinstorAnswers(@Nonnull ApiCallRcList answers) {
+        logLinstorAnswers(answers);
+        return answers.stream().filter(ApiCallRc::isError).findFirst().map(ApiCallRc::getMessage).orElse(null);
+    }
+
+    private String getDeviceName(DevelopersApi linstorApi, String rscName) throws ApiException {
+        List<ResourceWithVolumes> resources = linstorApi.viewResources(
+            Collections.emptyList(),
+            Collections.singletonList(rscName),
+            Collections.emptyList(),
+            null,
+            null,
+            null);
+        if (!resources.isEmpty() && !resources.get(0).getVolumes().isEmpty())
+        {
+            s_logger.info("Linstor: Created drbd device: " + resources.get(0).getVolumes().get(0).getDevicePath());
+            return resources.get(0).getVolumes().get(0).getDevicePath();
+        } else
+        {
+            s_logger.error("Linstor: viewResources didn't return resources or volumes.");
+            throw new CloudRuntimeException("Linstor: viewResources didn't return resources or volumes.");
+        }
+    }
+
+    private String createResource(VolumeInfo vol, StoragePoolVO storagePoolVO)
+    {
+        DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+
+        ResourceGroupSpawn rscGrpSpawn = new ResourceGroupSpawn();
+        final String rscName = LinstorUtil.RSC_PREFIX + vol.getUuid();
+        rscGrpSpawn.setResourceDefinitionName(rscName);
+        rscGrpSpawn.addVolumeSizesItem(vol.getSize() / 1024);
+
+        try
+        {
+            s_logger.debug("Linstor: Spawn resource " + rscName);
+            ApiCallRcList answers = linstorApi.resourceGroupSpawn(rscGrp, rscGrpSpawn);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx)
+        {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String cloneResource(long csCloneId, VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        // get the cached template on this storage
+        VMTemplateStoragePoolVO tmplPoolRef = _vmTemplatePoolDao.findByPoolTemplate(
+            storagePoolVO.getId(), csCloneId, null);
+
+        if (tmplPoolRef != null) {
+            final String cloneRes = LinstorUtil.RSC_PREFIX + tmplPoolRef.getLocalDownloadPath();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+            try {
+                s_logger.debug("Clone resource definition " + cloneRes + " to " + rscName);
+                ResourceDefinitionCloneRequest cloneRequest = new ResourceDefinitionCloneRequest();
+                cloneRequest.setName(rscName);
+                ResourceDefinitionCloneStarted cloneStarted = linstorApi.resourceDefinitionClone(
+                    cloneRes, cloneRequest);
+
+                checkLinstorAnswersThrow(cloneStarted.getMessages());
+
+                if (!CloneWaiter.waitFor(linstorApi, cloneStarted)) {
+                    throw new CloudRuntimeException("Clone for resource " + rscName + " failed.");
+                }
+
+                return getDeviceName(linstorApi, rscName);
+            } catch (ApiException apiEx) {
+                s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+                throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+            }
+        } else {
+            throw new CloudRuntimeException(
+                "Unable to find Linstor resource for the following template data-object ID: " + csCloneId);
+        }
+    }
+
+    private String createResourceFromSnapshot(long csSnapshotId, String rscName, StoragePoolVO storagePoolVO) {
+        final String rscGrp = storagePoolVO.getUserInfo() != null && !storagePoolVO.getUserInfo().isEmpty() ?
+            storagePoolVO.getUserInfo() : "DfltRscGrp";
+        final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
+
+        SnapshotVO snapshotVO = _snapshotDao.findById(csSnapshotId);
+        String snapName = LinstorUtil.RSC_PREFIX + snapshotVO.getUuid();
+        VolumeVO volumeVO = _volumeDao.findById(snapshotVO.getVolumeId());
+        String cloneRes = LinstorUtil.RSC_PREFIX + volumeVO.getPath();
+
+        try
+        {
+            s_logger.debug("Create new resource definition: " + rscName);
+            ResourceDefinitionCreate rdCreate = new ResourceDefinitionCreate();
+            ResourceDefinition rd = new ResourceDefinition();
+            rd.setName(rscName);
+            rd.setResourceGroupName(rscGrp);
+            rdCreate.setResourceDefinition(rd);
+            ApiCallRcList answers = linstorApi.resourceDefinitionCreate(rdCreate);
+            checkLinstorAnswersThrow(answers);
+
+            SnapshotRestore snapshotRestore = new SnapshotRestore();
+            snapshotRestore.toResource(rscName);
+
+            s_logger.debug("Create new volume definition for snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotsRestoreVolumeDefinition(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            // restore snapshot to new resource
+            s_logger.debug("Restore resource from snapshot: " + cloneRes + ":" + snapName);
+            answers = linstorApi.resourceSnapshotRestore(cloneRes, snapName, snapshotRestore);
+            checkLinstorAnswersThrow(answers);
+
+            return getDeviceName(linstorApi, rscName);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            throw new CloudRuntimeException(apiEx.getBestMessage(), apiEx);
+        }
+    }
+
+    private String createVolume(VolumeInfo volumeInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = getCsIdForCloning(volumeInfo.getId(), "cloneOfSnapshot");
+        long csTemplateId = getCsIdForCloning(volumeInfo.getId(), "cloneOfTemplate");
+
+        if (csSnapshotId > 0) {
+            return createResourceFromSnapshot(csSnapshotId, LinstorUtil.RSC_PREFIX + volumeInfo.getUuid(), storagePoolVO);
+        } else if (csTemplateId > 0) {
+            return cloneResource(csTemplateId, volumeInfo, storagePoolVO);
+        } else {
+            return createResource(volumeInfo, storagePoolVO);
+        }
+    }
+
+    private void handleSnapshotDetails(long csSnapshotId, String name, String value) {
+        _snapshotDetailsDao.removeDetail(csSnapshotId, name);
+        SnapshotDetailsVO snapshotDetails = new SnapshotDetailsVO(csSnapshotId, name, value, false);
+        _snapshotDetailsDao.persist(snapshotDetails);
+    }
+
+    private void addTempVolumeToDb(long csSnapshotId, String tempVolumeName) {
+        // TEMP_VOLUME_ID is needed, to find which temporary resource should be deleted after copying it on agent side
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, LinstorUtil.RSC_PREFIX + tempVolumeName);
+        // the iqn will be used on the agent side to copy from, even though linstor doesn't have anything to do with IQN
+        handleSnapshotDetails(csSnapshotId, DiskTO.IQN, tempVolumeName);
+    }
+
+    private void removeTempVolumeFromDb(long csSnapshotId) {
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+        if (snapshotDetails == null || snapshotDetails.getValue() == null) {
+            throw new CloudRuntimeException(
+                "'removeTempVolumeId' should not be invoked unless " + LinstorUtil.TEMP_VOLUME_ID + " exists.");
+        }
+
+        String originalVolumeId = snapshotDetails.getValue();
+
+        handleSnapshotDetails(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID, originalVolumeId);
+
+        _snapshotDetailsDao.remove(snapshotDetails.getId());
+    }
+
+    private void createVolumeFromSnapshot(SnapshotInfo snapshotInfo, StoragePoolVO storagePoolVO) {
+        long csSnapshotId = snapshotInfo.getId();
+
+        SnapshotDetailsVO snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, "tempVolume");
+
+        if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("create"))
+        {
+            final String csName = "Temp-" + snapshotInfo.getUuid();
+            final String tempRscName = LinstorUtil.RSC_PREFIX + csName;
+            createResourceFromSnapshot(csSnapshotId, tempRscName, storagePoolVO);
+
+            s_logger.debug("Temp resource created: " + tempRscName);
+            addTempVolumeToDb(csSnapshotId, csName);
+        }
+        else if (snapshotDetails != null && snapshotDetails.getValue() != null &&
+            snapshotDetails.getValue().equalsIgnoreCase("delete"))
+        {
+            snapshotDetails = _snapshotDetailsDao.findDetail(csSnapshotId, LinstorUtil.TEMP_VOLUME_ID);
+
+            deleteResourceDefinition(storagePoolVO, snapshotDetails.getValue());
+
+            s_logger.debug("Temp resource deleted: " + snapshotDetails.getValue());
+            removeTempVolumeFromDb(csSnapshotId);
+        }
+        else {
+            throw new CloudRuntimeException("Invalid state in 'createVolumeFromSnapshot(SnapshotInfo, StoragePoolVO)'");
+        }
+    }
+
+    @Override
+    public void createAsync(DataStore dataStore, DataObject vol, AsyncCompletionCallback<CreateCmdResult> callback)
+    {
+        String devPath = null;
+        String errMsg = null;
+        StoragePoolVO storagePool = _storagePoolDao.findById(dataStore.getId());
+
+        try
+        {
+            switch (vol.getType())
+            {
+                case VOLUME:
+                    VolumeInfo volumeInfo = (VolumeInfo) vol;
+                    VolumeVO volume = _volumeDao.findById(volumeInfo.getId());
+                    s_logger.debug("createAsync - creating volume");
+                    devPath = createVolume(volumeInfo, storagePool);
+                    volume.setFolder("/dev/");
+                    volume.setPoolId(storagePool.getId());
+                    volume.setUuid(vol.getUuid());
+                    volume.setPath(vol.getUuid());
+
+                    _volumeDao.update(volume.getId(), volume);
+                    break;
+                case SNAPSHOT:
+                    s_logger.debug("createAsync - SNAPSHOT");
+                    createVolumeFromSnapshot((SnapshotInfo) vol, storagePool);
+                    break;
+                case TEMPLATE:
+                    errMsg = "creating template - not supported";
+                    s_logger.error("createAsync - " + errMsg);
+                    break;
+                default:
+                    errMsg = "Invalid DataObjectType (" + vol.getType() + ") passed to createAsync";
+                    s_logger.error(errMsg);
+            }
+        } catch (Exception ex)
+        {
+            errMsg = ex.getMessage();
+
+            s_logger.error("createAsync: " + errMsg);
+            if (callback == null)
+            {
+                throw ex;
+            }
+        }
+
+        if (callback != null)
+        {
+            CreateCmdResult result = new CreateCmdResult(devPath, new Answer(null, errMsg == null, errMsg));
+            result.setResult(errMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public void revertSnapshot(
+        SnapshotInfo snapshot,
+        SnapshotInfo snapshotOnPrimaryStore,
+        AsyncCompletionCallback<CommandResult> callback)
+    {
+        s_logger.debug("Linstor: revertSnapshot");
+        final VolumeInfo volumeInfo = snapshot.getBaseVolume();
+        VolumeVO volumeVO = _volumeDao.findById(volumeInfo.getId());
+        if (volumeVO == null || volumeVO.getRemoved() != null) {
+            CommandResult commandResult = new CommandResult();
+            commandResult.setResult("The volume that the snapshot belongs to no longer exists.");
+            callback.complete(commandResult);
+            return;
+        }
+
+        String resultMsg;
+        try {
+            final StoragePool pool = (StoragePool) snapshot.getDataStore();
+            final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
+            final String snapName = LinstorUtil.RSC_PREFIX + snapshot.getUuid();
+            final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(pool.getHostAddress());
+
+            ApiCallRcList answers = linstorApi.resourceSnapshotRollback(rscName, snapName);
+            resultMsg = checkLinstorAnswers(answers);
+        } catch (ApiException apiEx) {
+            s_logger.error("Linstor: ApiEx - " + apiEx.getMessage());
+            resultMsg = apiEx.getBestMessage();
+        }
+
+        if (callback != null)
+        {
+            CommandResult result = new CommandResult();
+            result.setResult(resultMsg);
+            callback.complete(result);
+        }
+    }
+
+    @Override
+    public boolean canCopy(DataObject srcData, DataObject destData)
+    {
+        return false;
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback)
+    {
+        // as long as canCopy is false, this isn't called
+        s_logger.debug("Linstor: copyAsync with srcdata: " + srcData.getUuid());
+    }
+
+    @Override
+    public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback)

Review comment:
       ```
   [root@cloudstack-comp2 lib]# virsh domblkinfo --device /dev/drbd1001
    --domain i-2-253-VM --human
   Capacity:       10.002 GiB
   Allocation:     1.102 GiB
   Physical:       10.002 GiB
   ```
   
   lsblk in guest still tells me the old size




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@cloudstack.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [cloudstack] sureshanaparti edited a comment on pull request #4994: Linstor volume plugin

Posted by GitBox <gi...@apache.org>.
sureshanaparti edited a comment on pull request #4994:
URL: https://github.com/apache/cloudstack/pull/4994#issuecomment-842245607


   > > @rp- Please check if Linstor storage plugin supports all the functionalities mentioned in the cwiki. Can you add some storage references in cwiki ?
   > 
   > What do you mean with "storage references" ?
   
   @rp- I mean some references, for the docs to know about the Linstor storage. I found one mentioned in cwiki page, and  updated the references section.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org