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

dahn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new 12d9c26747d Added support for storpool_qos service  (#8755)
12d9c26747d is described below

commit 12d9c26747d278607654eedf43d8a7a623444be1
Author: slavkap <51903378+slav...@users.noreply.github.com>
AuthorDate: Thu Aug 29 10:23:25 2024 +0300

    Added support for storpool_qos service  (#8755)
---
 .../api/storage/PrimaryDataStoreDriver.java        |   6 +
 plugins/storage/volume/storpool/README.md          |  40 ++
 .../storage/datastore/api/StorPoolVolumeDef.java   | 109 +++++
 .../driver/StorPoolPrimaryDataStoreDriver.java     | 271 ++++++++--
 .../storage/datastore/util/StorPoolHelper.java     |   3 +-
 .../storage/datastore/util/StorPoolUtil.java       |  34 +-
 .../com/cloud/storage/ResizeVolumePayload.java     |   8 +
 .../com/cloud/storage/VolumeApiServiceImpl.java    |  16 +-
 test/integration/plugins/storpool/sp_util.py       |  45 ++
 .../plugins/storpool/test_storpool_tiers.py        | 544 +++++++++++++++++++++
 tools/marvin/marvin/lib/base.py                    |   5 +-
 11 files changed, 1021 insertions(+), 60 deletions(-)

diff --git 
a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java
 
b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java
index d52c656f6db..2011b1f08fb 100644
--- 
a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java
+++ 
b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java
@@ -24,6 +24,7 @@ import 
org.apache.cloudstack.framework.async.AsyncCompletionCallback;
 import org.apache.cloudstack.storage.command.CommandResult;
 
 import com.cloud.host.Host;
+import com.cloud.offering.DiskOffering;
 import com.cloud.storage.StoragePool;
 import com.cloud.storage.Volume;
 import com.cloud.storage.Storage.StoragePoolType;
@@ -199,4 +200,9 @@ public interface PrimaryDataStoreDriver extends 
DataStoreDriver {
     default long getVolumeSizeRequiredOnPool(long volumeSize, Long 
templateSize, boolean isEncryptionRequired) {
         return volumeSize;
     }
+    default boolean informStorageForDiskOfferingChange() {
+        return false;
+    }
+
+    default void updateStorageWithTheNewDiskOffering(Volume volume, 
DiskOffering newDiskOffering) {}
 }
diff --git a/plugins/storage/volume/storpool/README.md 
b/plugins/storage/volume/storpool/README.md
index e5b84786c7c..5cad0069621 100644
--- a/plugins/storage/volume/storpool/README.md
+++ b/plugins/storage/volume/storpool/README.md
@@ -345,6 +345,46 @@ corresponding system disk offering.
 
 CloudStack has no way to specify max BW. Do they want to be able to specify 
max BW only is sufficient.
 
+================================================================================
+
+StorPool provides the ‘storpool_qos’ service ([QoS user 
guide](https://kb.storpool.com/storpool_misc/qos.html#storpool-qos-user-guide)) 
that tracks and configures the storage tier for all volumes based on a 
specifically provided `qc` tag specifying the storage tier for each volume.
+
+To manage the QoS limits with a `qc` tag, you have to add a `qc` tag resource 
detail to each disk offering to which a tier should be applied, with a key 
`SP_QOSCLASS` and the value from the configuration file for the `storpool_qos` 
service:
+
+       add resourcedetail resourceid={diskofferingid} 
details[0].key=SP_QOSCLASS details[0].value={the name of the tier from the 
config} resourcetype=DiskOffering
+
+To change the tier via CloudStack, you can use the CloudStack API call 
`changeOfferingForVolume`. The size is required, but the user could use the 
current volume size. Example:
+
+       change offeringforvolume id={The UUID of the Volume} 
diskofferingid={The UUID of the disk offering} size={The current or a new size 
for the volume}
+
+Users who were using the offerings to change the StorPool template via the 
`SP_TEMPLATE` detail, will continue to have this functionality but should use 
`changeOfferingForVolume` API call instead of:
+ - `resizeVolume` API call for DATA disk
+ - `scaleVirtualMachine` API call for ROOT disk
+
+
+If the disk offering has both `SP_TEMPLATE` and `SP_QOSCLASS` defined, the 
`SP_QOSCLASS` detail will be prioritised, setting the volume’s QoS using the 
respective ‘qc’ tag value. In case the QoS for a volume is changed manually, 
the ‘storpool_qos’ service will automatically reset the QoS limits following 
the ‘qc’ tag value once per minute.
+
+<h4>Usage</h4>
+
+Creating Disk Offering for each tier.
+
+Go to Service Offerings > Disk Offering > Add disk offering.
+
+Add disk offering detail with API call in CloudStack CLI.
+
+       add resourcedetail resourcetype=diskoffering resourceid=$UUID 
details[0].key=SP_QOSCLASS details[0].value=$Tier Name
+
+
+Creating VM with QoS
+
+Deploy virtual machine: Go to Compute> Instances> Add Instances.
+ - For the ROOT volume, choose the option `Override disk offering`. This will 
set the required `qc` tag from the disk offering (DO) detail.
+
+Creating DATA disk with QoS
+ - Create volume via GUI/CLI and choose a disk offering which has the required 
`SP_QOSCLASS` detail
+
+To update the tier of a ROOT/DATA volume go to Storage> Volumes and select the 
Volume and click on the Change disk offering for the volume in the upper right 
corner.
+
 ## Supported operations for Volume encryption
 
 Supported Virtual machine operations - live migration of VM to another host, 
virtual machine snapshots (group snapshot without memory), revert VM snapshot, 
delete VM snapshot
diff --git 
a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/api/StorPoolVolumeDef.java
 
b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/api/StorPoolVolumeDef.java
new file mode 100644
index 00000000000..456f5b90639
--- /dev/null
+++ 
b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/api/StorPoolVolumeDef.java
@@ -0,0 +1,109 @@
+// 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.api;
+
+import java.io.Serializable;
+import java.util.Map;
+
+public class StorPoolVolumeDef implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+    private transient String name;
+    private Long size;
+    private Map<String, String> tags;
+    private String parent;
+    private Long iops;
+    private String template;
+    private String baseOn;
+    private String rename;
+    private Boolean shrinkOk;
+
+    public StorPoolVolumeDef() {
+    }
+
+    public StorPoolVolumeDef(String name, Long size, Map<String, String> tags, 
String parent, Long iops, String template,
+                             String baseOn, String rename, Boolean shrinkOk) {
+        super();
+        this.name = name;
+        this.size = size;
+        this.tags = tags;
+        this.parent = parent;
+        this.iops = iops;
+        this.template = template;
+        this.baseOn = baseOn;
+        this.rename = rename;
+        this.shrinkOk = shrinkOk;
+    }
+
+    public String getName() {
+        return name;
+    }
+    public void setName(String name) {
+        this.name = name;
+    }
+    public Long getSize() {
+        return size;
+    }
+    public void setSize(Long size) {
+        this.size = size;
+    }
+    public Map<String, String> getTags() {
+        return tags;
+    }
+    public void setTags(Map<String, String> tags) {
+        this.tags = tags;
+    }
+    public String getParent() {
+        return parent;
+    }
+    public void setParent(String parent) {
+        this.parent = parent;
+    }
+    public Long getIops() {
+        return iops;
+    }
+    public void setIops(Long iops) {
+        this.iops = iops;
+    }
+    public String getTemplate() {
+        return template;
+    }
+    public void setTemplate(String template) {
+        this.template = template;
+    }
+    public String getBaseOn() {
+        return baseOn;
+    }
+    public void setBaseOn(String baseOn) {
+        this.baseOn = baseOn;
+    }
+    public String getRename() {
+        return rename;
+    }
+    public void setRename(String rename) {
+        this.rename = rename;
+    }
+
+    public Boolean getShrinkOk() {
+        return shrinkOk;
+    }
+
+    public void setShrinkOk(Boolean shrinkOk) {
+        this.shrinkOk = shrinkOk;
+    }
+}
diff --git 
a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java
 
b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java
index b9c0b73f78d..631186636ca 100644
--- 
a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java
+++ 
b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/driver/StorPoolPrimaryDataStoreDriver.java
@@ -39,12 +39,15 @@ 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.framework.config.dao.ConfigurationDao;
+import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO;
+import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao;
 import org.apache.cloudstack.storage.RemoteHostEndPoint;
 import org.apache.cloudstack.storage.command.CommandResult;
 import org.apache.cloudstack.storage.command.CopyCmdAnswer;
 import org.apache.cloudstack.storage.command.CreateObjectAnswer;
 import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
 import org.apache.cloudstack.storage.datastore.api.StorPoolSnapshotDef;
+import org.apache.cloudstack.storage.datastore.api.StorPoolVolumeDef;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO;
@@ -81,12 +84,18 @@ import com.cloud.agent.api.to.DataStoreTO;
 import com.cloud.agent.api.to.DataTO;
 import com.cloud.agent.api.to.StorageFilerTO;
 import com.cloud.dc.dao.ClusterDao;
+import com.cloud.exception.StorageUnavailableException;
 import com.cloud.host.Host;
 import com.cloud.host.HostVO;
 import com.cloud.host.dao.HostDao;
 import com.cloud.hypervisor.kvm.storage.StorPoolStorageAdaptor;
+import com.cloud.offering.DiskOffering;
 import com.cloud.server.ResourceTag;
 import com.cloud.server.ResourceTag.ResourceObjectType;
+import com.cloud.service.ServiceOfferingDetailsVO;
+import com.cloud.service.ServiceOfferingVO;
+import com.cloud.service.dao.ServiceOfferingDao;
+import com.cloud.service.dao.ServiceOfferingDetailsDao;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.ResizeVolumePayload;
 import com.cloud.storage.Snapshot;
@@ -156,6 +165,12 @@ public class StorPoolPrimaryDataStoreDriver implements 
PrimaryDataStoreDriver {
     private StoragePoolHostDao storagePoolHostDao;
     @Inject
     DataStoreManager dataStoreManager;
+    @Inject
+    private DiskOfferingDetailsDao diskOfferingDetailsDao;
+    @Inject
+    private ServiceOfferingDetailsDao serviceOfferingDetailDao;
+    @Inject
+    private ServiceOfferingDao serviceOfferingDao;
 
     private SnapshotDataStoreVO getSnapshotImageStoreRef(long snapshotId, long 
zoneId) {
         List<SnapshotDataStoreVO> snaps = 
snapshotDataStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image);
@@ -259,15 +274,25 @@ public class StorPoolPrimaryDataStoreDriver implements 
PrimaryDataStoreDriver {
     public void createAsync(DataStore dataStore, DataObject data, 
AsyncCompletionCallback<CreateCmdResult> callback) {
         String path = null;
         Answer answer;
+        String tier = null;
+        String template = null;
         if (data.getType() == DataObjectType.VOLUME) {
             try {
                 VolumeInfo vinfo = (VolumeInfo)data;
                 String name = vinfo.getUuid();
                 Long size = vinfo.getPassphraseId() == null ? vinfo.getSize() 
: vinfo.getSize() + 2097152;
+                Long vmId = vinfo.getInstanceId();
+
                 SpConnectionDesc conn = 
StorPoolUtil.getSpConnection(dataStore.getUuid(), dataStore.getId(), 
storagePoolDetailsDao, primaryStoreDao);
 
-                StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriver.createAsync 
volume: name=%s, uuid=%s, isAttached=%s vm=%s, payload=%s, template: %s", 
vinfo.getName(), vinfo.getUuid(), vinfo.isAttachedVM(), 
vinfo.getAttachedVmName(), vinfo.getpayload(), conn.getTemplateName());
-                SpApiResponse resp = StorPoolUtil.volumeCreate(name, null, 
size, getVMInstanceUUID(vinfo.getInstanceId()), null, "volume", 
vinfo.getMaxIops(), conn);
+                if (vinfo.getDiskOfferingId() != null) {
+                    tier = 
getTierFromOfferingDetail(vinfo.getDiskOfferingId());
+                    if (tier == null) {
+                        template = 
getTemplateFromOfferingDetail(vinfo.getDiskOfferingId());
+                    }
+                }
+
+                SpApiResponse resp = createStorPoolVolume(template, tier, 
vinfo, name, size, vmId, conn);
                 if (resp.getError() == null) {
                     String volumeName = StorPoolUtil.getNameFromResponse(resp, 
false);
                     path = StorPoolUtil.devPath(volumeName);
@@ -298,6 +323,26 @@ public class StorPoolPrimaryDataStoreDriver implements 
PrimaryDataStoreDriver {
         }
     }
 
+    private SpApiResponse createStorPoolVolume(String template, String tier, 
VolumeInfo vinfo, String name, Long size,
+            Long vmId, SpConnectionDesc conn) {
+        SpApiResponse resp = new SpApiResponse();
+        Map<String, String> tags = StorPoolHelper.addStorPoolTags(name, 
getVMInstanceUUID(vmId), "volume", getVcPolicyTag(vmId), tier);
+        if (tier != null || template != null) {
+            StorPoolUtil.spLog(
+                    "Creating volume [%s] with template [%s] or tier tags [%s] 
described in disk/service offerings details",
+                    vinfo.getUuid(), template, tier);
+            resp = StorPoolUtil.volumeCreate(size, null, template, tags, conn);
+        } else {
+            StorPoolUtil.spLog(
+                    "StorpoolPrimaryDataStoreDriver.createAsync volume: 
name=%s, uuid=%s, isAttached=%s vm=%s, payload=%s, template: %s",
+                    vinfo.getName(), vinfo.getUuid(), vinfo.isAttachedVM(), 
vinfo.getAttachedVmName(),
+                    vinfo.getpayload(), conn.getTemplateName());
+            resp = StorPoolUtil.volumeCreate(name, null, size, 
getVMInstanceUUID(vinfo.getInstanceId()), null,
+                    "volume", vinfo.getMaxIops(), conn);
+        }
+        return resp;
+    }
+
     private void updateVolume(DataStore dataStore, String path, VolumeInfo 
vinfo) {
         VolumeVO volume = volumeDao.findById(vinfo.getId());
         volume.setPoolId(dataStore.getId());
@@ -336,66 +381,109 @@ public class StorPoolPrimaryDataStoreDriver implements 
PrimaryDataStoreDriver {
     public void resize(DataObject data, 
AsyncCompletionCallback<CreateCmdResult> callback) {
         String path = null;
         String err = null;
-        ResizeVolumeAnswer answer = null;
 
         if (data.getType() == DataObjectType.VOLUME) {
             VolumeObject vol = (VolumeObject)data;
-            StoragePool pool = (StoragePool)data.getDataStore();
-            ResizeVolumePayload payload = 
(ResizeVolumePayload)vol.getpayload();
+            path = vol.getPath();
 
-            final String name = 
StorPoolStorageAdaptor.getVolumeNameFromPath(vol.getPath(), true);
-            final long oldSize = vol.getSize();
-            Long oldMaxIops = vol.getMaxIops();
+            err = resizeVolume(data, path, vol);
+        } else {
+            err = String.format("Invalid object type \"%s\"  passed to 
resize", data.getType());
+        }
 
-            try {
-                SpConnectionDesc conn = 
StorPoolUtil.getSpConnection(data.getDataStore().getUuid(), 
data.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
+        CreateCmdResult res = new CreateCmdResult(path, new Answer(null, err 
!= null, err));
+        res.setResult(err);
+        callback.complete(res);
+    }
 
-                long maxIops = payload.newMaxIops == null ? Long.valueOf(0) : 
payload.newMaxIops;
+    private String resizeVolume(DataObject data, String path, VolumeObject 
vol) {
+        String err = null;
+        ResizeVolumePayload payload = (ResizeVolumePayload)vol.getpayload();
+        boolean needResize = vol.getSize() != payload.newSize;
 
-                StorPoolUtil.spLog("StorpoolPrimaryDataStoreDriverImpl.resize: 
name=%s, uuid=%s, oldSize=%d, newSize=%s, shrinkOk=%s, maxIops=%s", name, 
vol.getUuid(), oldSize, payload.newSize, payload.shrinkOk, maxIops);
+        final String name = StorPoolStorageAdaptor.getVolumeNameFromPath(path, 
true);
+        final long oldSize = vol.getSize();
+        Long oldMaxIops = vol.getMaxIops();
 
-                SpApiResponse resp = StorPoolUtil.volumeUpdate(name, 
payload.newSize, payload.shrinkOk, maxIops, conn);
-                if (resp.getError() != null) {
-                    err = String.format("Could not resize StorPool volume %s. 
Error: %s", name, resp.getError());
-                } else {
-                    StorPoolResizeVolumeCommand resizeCmd = new 
StorPoolResizeVolumeCommand(vol.getPath(), new StorageFilerTO(pool), 
vol.getSize(), payload.newSize, payload.shrinkOk,
-                            payload.instanceName, payload.hosts == null ? 
false : true);
-                    answer = (ResizeVolumeAnswer) storageMgr.sendToPool(pool, 
payload.hosts, resizeCmd);
+        try {
+            SpConnectionDesc conn = 
StorPoolUtil.getSpConnection(data.getDataStore().getUuid(), 
data.getDataStore().getId(), storagePoolDetailsDao, primaryStoreDao);
 
-                    if (answer == null || !answer.getResult()) {
-                        err = answer != null ? answer.getDetails() : "return a 
null answer, resize failed for unknown reason";
-                    } else {
-                        path = 
StorPoolUtil.devPath(StorPoolUtil.getNameFromResponse(resp, false));
-
-                        vol.setSize(payload.newSize);
-                        vol.update();
-                        if (payload.newMaxIops != null) {
-                            VolumeVO volume = volumeDao.findById(vol.getId());
-                            volume.setMaxIops(payload.newMaxIops);
-                            volumeDao.update(volume.getId(), volume);
-                        }
+            err = updateStorPoolVolume(vol, payload, conn);
+            if (err == null && needResize) {
+                err = notifyQemuForTheNewSize(data, err, vol, payload);
+            }
 
-                        updateStoragePool(vol.getPoolId(), payload.newSize - 
oldSize);
-                    }
-                }
-                if (err != null) {
-                    // try restoring volume to its initial size
-                    resp = StorPoolUtil.volumeUpdate(name, oldSize, true, 
oldMaxIops, conn);
-                    if (resp.getError() != null) {
-                        logger.debug(String.format("Could not resize StorPool 
volume %s back to its original size. Error: %s", name, resp.getError()));
-                    }
+            if (err != null) {
+                // try restoring volume to its initial size
+                SpApiResponse response = StorPoolUtil.volumeUpdate(name, 
oldSize, true, oldMaxIops, conn);
+                if (response.getError() != null) {
+                    logger.debug(String.format("Could not resize StorPool 
volume %s back to its original size. Error: %s", name, response.getError()));
                 }
-            } catch (Exception e) {
-                logger.debug("sending resize command failed", e);
-                err = e.toString();
             }
+        } catch (Exception e) {
+            logger.debug("sending resize command failed", e);
+            err = e.toString();
+        }
+        return err;
+    }
+
+    private String notifyQemuForTheNewSize(DataObject data, String err, 
VolumeObject vol, ResizeVolumePayload payload)
+            throws StorageUnavailableException {
+        StoragePool pool = (StoragePool)data.getDataStore();
+
+        StorPoolResizeVolumeCommand resizeCmd = new 
StorPoolResizeVolumeCommand(vol.getPath(), new StorageFilerTO(pool), 
vol.getSize(), payload.newSize, payload.shrinkOk,
+                payload.instanceName, payload.hosts == null ? false : true);
+        ResizeVolumeAnswer answer = (ResizeVolumeAnswer) 
storageMgr.sendToPool(pool, payload.hosts, resizeCmd);
+
+        if (answer == null || !answer.getResult()) {
+            err = answer != null ? answer.getDetails() : "return a null 
answer, resize failed for unknown reason";
+        }
+        return err;
+    }
+
+    private String updateStorPoolVolume(VolumeObject vol, ResizeVolumePayload 
payload, SpConnectionDesc conn) {
+        String err = null;
+        String name = 
StorPoolStorageAdaptor.getVolumeNameFromPath(vol.getPath(), true);
+        Long newDiskOfferingId = payload.getNewDiskOfferingId();
+        String tier = null;
+        String template = null;
+        if (newDiskOfferingId != null) {
+            tier = getTierFromOfferingDetail(newDiskOfferingId);
+            if (tier == null) {
+                template = getTemplateFromOfferingDetail(newDiskOfferingId);
+            }
+        }
+        SpApiResponse resp = new SpApiResponse();
+        if (tier != null || template != null) {
+            Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, 
null, null, null, tier);
+            StorPoolVolumeDef spVolume = new StorPoolVolumeDef(name, 
payload.newSize, tags, null, null, template, null, null,
+                    payload.shrinkOk);
+            resp = StorPoolUtil.volumeUpdate(spVolume, conn);
         } else {
-            err = String.format("Invalid object type \"%s\"  passed to 
resize", data.getType());
+            long maxIops = payload.newMaxIops == null ? Long.valueOf(0) : 
payload.newMaxIops;
+
+            StorPoolVolumeDef spVolume = new StorPoolVolumeDef(name, 
payload.newSize, null, null, maxIops, null, null, null,
+                    payload.shrinkOk);
+            StorPoolUtil.spLog(
+                    "StorpoolPrimaryDataStoreDriverImpl.resize: name=%s, 
uuid=%s, oldSize=%d, newSize=%s, shrinkOk=%s, maxIops=%s",
+                    name, vol.getUuid(), vol.getSize(), payload.newSize, 
payload.shrinkOk, maxIops);
+
+            resp = StorPoolUtil.volumeUpdate(spVolume, conn);
         }
+        if (resp.getError() != null) {
+            err = String.format("Could not resize StorPool volume %s. Error: 
%s", name, resp.getError());
+        } else {
+            vol.setSize(payload.newSize);
+            vol.update();
+            if (payload.newMaxIops != null) {
+                VolumeVO volume = volumeDao.findById(vol.getId());
+                volume.setMaxIops(payload.newMaxIops);
+                volumeDao.update(volume.getId(), volume);
+            }
 
-        CreateCmdResult res = new CreateCmdResult(path, answer);
-        res.setResult(err);
-        callback.complete(res);
+            updateStoragePool(vol.getPoolId(), payload.newSize - 
vol.getSize());
+        }
+        return err;
     }
 
     @Override
@@ -772,8 +860,30 @@ public class StorPoolPrimaryDataStoreDriver implements 
PrimaryDataStoreDriver {
                 }
                 StorPoolUtil.spLog(String.format("volume size is: %d", size));
                 Long vmId = vinfo.getInstanceId();
-                SpApiResponse resp = StorPoolUtil.volumeCreate(name, 
parentName, size, getVMInstanceUUID(vmId), getVcPolicyTag(vmId),
-                        "volume", vinfo.getMaxIops(), conn);
+
+                String template = null;
+                String tier = null;
+                SpApiResponse resp = new SpApiResponse();
+
+                if (vinfo.getDiskOfferingId() != null) {
+                    tier = 
getTierFromOfferingDetail(vinfo.getDiskOfferingId());
+                    if (tier == null) {
+                        template = 
getTemplateFromOfferingDetail(vinfo.getDiskOfferingId());
+                    }
+                }
+
+                if (tier != null || template != null) {
+                    Map<String, String> tags = 
StorPoolHelper.addStorPoolTags(name, getVMInstanceUUID(vmId), "volume", 
getVcPolicyTag(vmId), tier);
+
+                    StorPoolUtil.spLog(
+                            "Creating volume [%s] with template [%s] or tier 
tags [%s] described in disk/service offerings details",
+                            vinfo.getUuid(), template, tier);
+                    resp = StorPoolUtil.volumeCreate(size, parentName, 
template, tags, conn);
+                } else {
+                    resp = StorPoolUtil.volumeCreate(name, parentName, size, 
getVMInstanceUUID(vmId),
+                            getVcPolicyTag(vmId), "volume", 
vinfo.getMaxIops(), conn);
+                }
+
                 if (resp.getError() == null) {
                     updateStoragePool(dstData.getDataStore().getId(), 
vinfo.getSize());
                     updateVolumePoolType(vinfo);
@@ -1255,4 +1365,67 @@ public class StorPoolPrimaryDataStoreDriver implements 
PrimaryDataStoreDriver {
             StorPoolUtil.spLog("The volume [%s] is detach from all clusters 
[%s]", volName, resp);
         }
     }
+
+    @Override
+    public boolean informStorageForDiskOfferingChange() {
+        return true;
+    }
+
+    @Override
+    public void updateStorageWithTheNewDiskOffering(Volume volume, 
DiskOffering newDiskOffering) {
+        if (newDiskOffering == null) {
+            return;
+        }
+
+        StoragePoolVO pool = primaryStoreDao.findById(volume.getPoolId());
+        if (pool == null) {
+            return;
+        }
+
+        String tier = getTierFromOfferingDetail(newDiskOffering.getId());
+        String template = null;
+        if (tier == null) {
+            template = getTemplateFromOfferingDetail(newDiskOffering.getId());
+        }
+        if (tier == null && template == null) {
+            return;
+        }
+        SpConnectionDesc conn = StorPoolUtil.getSpConnection(pool.getUuid(), 
pool.getId(), storagePoolDetailsDao, primaryStoreDao);
+        StorPoolUtil.spLog("Updating volume [%s] with tier tag [%s] or 
template [%s] from Disk offering", volume.getId(), tier, template);
+        String volumeName = 
StorPoolStorageAdaptor.getVolumeNameFromPath(volume.getPath(), true);
+        Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, null, 
null, null, tier);
+        StorPoolVolumeDef spVolume = new StorPoolVolumeDef(volumeName, null, 
tags, null, null, template, null, null, null);
+        SpApiResponse response = StorPoolUtil.volumeUpdate(spVolume, conn);
+        if (response.getError() != null) {
+            StorPoolUtil.spLog("Could not update volume [%s] with tier tag 
[%s] or template [%s] from Disk offering due to [%s]", volume.getId(), tier, 
template, response.getError());
+        }
+    }
+
+    private String getTemplateFromOfferingDetail(Long diskOfferingId) {
+        String template = null;
+        DiskOfferingDetailVO diskOfferingDetail = 
diskOfferingDetailsDao.findDetail(diskOfferingId, StorPoolUtil.SP_TEMPLATE);
+        if (diskOfferingDetail == null ) {
+            ServiceOfferingVO serviceOffering = 
serviceOfferingDao.findServiceOfferingByComputeOnlyDiskOffering(diskOfferingId, 
true);
+            if (serviceOffering != null) {
+                ServiceOfferingDetailsVO serviceOfferingDetail = 
serviceOfferingDetailDao.findDetail(serviceOffering.getId(), 
StorPoolUtil.SP_TEMPLATE);
+                if (serviceOfferingDetail != null) {
+                    template = serviceOfferingDetail.getValue();
+                }
+            }
+        } else {
+            template = diskOfferingDetail.getValue();
+        }
+        return template;
+    }
+
+    private String getTierFromOfferingDetail(Long diskOfferingId) {
+        String tier = null;
+        DiskOfferingDetailVO diskOfferingDetail = 
diskOfferingDetailsDao.findDetail(diskOfferingId, StorPoolUtil.SP_TIER);
+        if (diskOfferingDetail == null ) {
+            return tier;
+        } else {
+            tier = diskOfferingDetail.getValue();
+        }
+        return tier;
+    }
 }
diff --git 
a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolHelper.java
 
b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolHelper.java
index 5a84e699f52..3113ae8fdaa 100644
--- 
a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolHelper.java
+++ 
b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolHelper.java
@@ -163,11 +163,12 @@ public class StorPoolHelper {
         return null;
     }
 
-    public static Map<String, String> addStorPoolTags(String name, String 
vmUuid, String csTag, String vcPolicy) {
+    public static Map<String, String> addStorPoolTags(String name, String 
vmUuid, String csTag, String vcPolicy, String qcTier) {
         Map<String, String> tags = new HashMap<>();
         tags.put("uuid", name);
         tags.put("cvm", vmUuid);
         tags.put(StorPoolUtil.SP_VC_POLICY, vcPolicy);
+        tags.put("qc", qcTier);
         if (csTag != null) {
             tags.put("cs", csTag);
         }
diff --git 
a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java
 
b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java
index 42809daec57..97f4e2fe155 100644
--- 
a/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java
+++ 
b/plugins/storage/volume/storpool/src/main/java/org/apache/cloudstack/storage/datastore/util/StorPoolUtil.java
@@ -30,6 +30,7 @@ import com.google.gson.JsonParser;
 import com.google.gson.JsonPrimitive;
 
 import org.apache.cloudstack.storage.datastore.api.StorPoolSnapshotDef;
+import org.apache.cloudstack.storage.datastore.api.StorPoolVolumeDef;
 import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
 import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
@@ -135,6 +136,7 @@ public class StorPoolUtil {
 
     public static final String DELAY_DELETE = "delayDelete";
 
+    public static final String SP_TIER = "SP_QOSCLASS";
 
     public static enum StorpoolRights {
         RO("ro"), RW("rw"), DETACH("detach");
@@ -499,7 +501,19 @@ public class StorPoolUtil {
         json.put("parent", parentName);
         json.put("size", size);
         json.put("template", conn.getTemplateName());
-        Map<String, String> tags = StorPoolHelper.addStorPoolTags(name, 
vmUuid, csTag, vcPolicy);
+        Map<String, String> tags = StorPoolHelper.addStorPoolTags(name, 
vmUuid, csTag, vcPolicy, null);
+        json.put("tags", tags);
+        return POST("MultiCluster/VolumeCreate", json, conn);
+    }
+
+    public static SpApiResponse volumeCreate(Long size, String parentName, 
String template, Map<String,String> tags, SpConnectionDesc conn) {
+        template = template != null ? template : conn.getTemplateName();
+
+        Map<String, Object> json = new LinkedHashMap<>();
+        json.put("name", "");
+        json.put("parent", parentName);
+        json.put("size", size);
+        json.put("template", template);
         json.put("tags", tags);
         return POST("MultiCluster/VolumeCreate", json, conn);
     }
@@ -523,7 +537,7 @@ public class StorPoolUtil {
             json.put("iops", iops);
         }
         json.put("template", conn.getTemplateName());
-        Map<String, String> tags = StorPoolHelper.addStorPoolTags(name, 
cvmTag, csTag, vcPolicyTag);
+        Map<String, String> tags = StorPoolHelper.addStorPoolTags(name, 
cvmTag, csTag, vcPolicyTag, null);
         json.put("tags", tags);
         return POST("MultiCluster/VolumeCreate", json, conn);
     }
@@ -551,7 +565,7 @@ public class StorPoolUtil {
 
     public static SpApiResponse volumeRemoveTags(String name, SpConnectionDesc 
conn) {
         Map<String, Object> json = new HashMap<>();
-        Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, "", 
null, "");
+        Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, "", 
null, "", null);
         json.put("tags", tags);
         return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
     }
@@ -559,7 +573,7 @@ public class StorPoolUtil {
     public static SpApiResponse volumeUpdateIopsAndTags(final String name, 
final String uuid, Long iops,
             SpConnectionDesc conn, String vcPolicy) {
         Map<String, Object> json = new HashMap<>();
-        Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, uuid, 
null, vcPolicy);
+        Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, uuid, 
null, vcPolicy, null);
         json.put("iops", iops);
         json.put("tags", tags);
         return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
@@ -567,14 +581,14 @@ public class StorPoolUtil {
 
     public static SpApiResponse volumeUpdateCvmTags(final String name, final 
String uuid, SpConnectionDesc conn) {
         Map<String, Object> json = new HashMap<>();
-        Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, uuid, 
null, null);
+        Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, uuid, 
null, null, null);
         json.put("tags", tags);
         return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
     }
 
     public static SpApiResponse volumeUpdateVCTags(final String name, 
SpConnectionDesc conn, String vcPolicy) {
         Map<String, Object> json = new HashMap<>();
-        Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, null, 
null, vcPolicy);
+        Map<String, String> tags = StorPoolHelper.addStorPoolTags(null, null, 
null, vcPolicy, null);
         json.put("tags", tags);
         return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
     }
@@ -585,10 +599,14 @@ public class StorPoolUtil {
         return POST("MultiCluster/VolumeUpdate/" + name, json, conn);
     }
 
+    public static SpApiResponse volumeUpdate(StorPoolVolumeDef volume, 
SpConnectionDesc conn) {
+        return POST("MultiCluster/VolumeUpdate/" + volume.getName(), volume, 
conn);
+    }
+
     public static SpApiResponse volumeSnapshot(final String volumeName, final 
String snapshotName, String vmUuid,
             String csTag, String vcPolicy, SpConnectionDesc conn) {
         Map<String, Object> json = new HashMap<>();
-        Map<String, String> tags = 
StorPoolHelper.addStorPoolTags(snapshotName, vmUuid, csTag, vcPolicy);
+        Map<String, String> tags = 
StorPoolHelper.addStorPoolTags(snapshotName, vmUuid, csTag, vcPolicy, null);
         json.put("name", "");
         json.put("tags", tags);
 
@@ -602,7 +620,7 @@ public class StorPoolUtil {
     public static SpApiResponse volumesGroupSnapshot(final 
List<VolumeObjectTO> volumeTOs, final String vmUuid,
             final String snapshotName, String csTag, SpConnectionDesc conn) {
         Map<String, Object> json = new LinkedHashMap<>();
-        Map<String, String> tags = 
StorPoolHelper.addStorPoolTags(snapshotName, vmUuid, csTag, null);
+        Map<String, String> tags = 
StorPoolHelper.addStorPoolTags(snapshotName, vmUuid, csTag, null, null);
         List<Map<String, Object>> volumes = new ArrayList<>();
         for (VolumeObjectTO volumeTO : volumeTOs) {
             Map<String, Object> vol = new LinkedHashMap<>();
diff --git a/server/src/main/java/com/cloud/storage/ResizeVolumePayload.java 
b/server/src/main/java/com/cloud/storage/ResizeVolumePayload.java
index 84dcd302bdd..32aa09be8b6 100644
--- a/server/src/main/java/com/cloud/storage/ResizeVolumePayload.java
+++ b/server/src/main/java/com/cloud/storage/ResizeVolumePayload.java
@@ -46,4 +46,12 @@ public class ResizeVolumePayload {
         this(newSize, newMinIops, newMaxIops, newHypervisorSnapshotReserve, 
shrinkOk, instanceName, hosts, isManaged);
         this.newDiskOfferingId = newDiskOfferingId;
     }
+
+    public Long getNewDiskOfferingId() {
+        return newDiskOfferingId;
+    }
+
+    public void setNewDiskOfferingId(Long newDiskOfferingId) {
+        this.newDiskOfferingId = newDiskOfferingId;
+    }
 }
diff --git a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java 
b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
index e653836b729..a925f5cecf4 100644
--- a/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
+++ b/server/src/main/java/com/cloud/storage/VolumeApiServiceImpl.java
@@ -2062,6 +2062,8 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
         if (!volumeMigrateRequired && !volumeResizeRequired) {
             _volsDao.updateDiskOffering(volume.getId(), 
newDiskOffering.getId());
             volume = _volsDao.findById(volume.getId());
+            updateStorageWithTheNewDiskOffering(volume, newDiskOffering);
+
             return volume;
         }
 
@@ -2098,6 +2100,18 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
         return volume;
     }
 
+    private void updateStorageWithTheNewDiskOffering(VolumeVO volume, 
DiskOfferingVO newDiskOffering) {
+        DataStore dataStore = dataStoreMgr.getDataStore(volume.getPoolId(), 
DataStoreRole.Primary);
+        DataStoreDriver dataStoreDriver = dataStore != null ? 
dataStore.getDriver() : null;
+
+        if (dataStoreDriver instanceof PrimaryDataStoreDriver) {
+            PrimaryDataStoreDriver storageDriver = 
(PrimaryDataStoreDriver)dataStoreDriver;
+            if (storageDriver.informStorageForDiskOfferingChange()) {
+                storageDriver.updateStorageWithTheNewDiskOffering(volume, 
newDiskOffering);
+            }
+        }
+    }
+
     /**
      * This method is to compare long values, in miniops and maxiops a or b 
can be null or 0.
      * Use this method to treat 0 and null as same
@@ -2331,7 +2345,7 @@ public class VolumeApiServiceImpl extends ManagerBase 
implements VolumeApiServic
              * the actual disk size.
              */
             if (currentSize > newSize) {
-                if (volume != null && 
ImageFormat.QCOW2.equals(volume.getFormat()) && 
!Volume.State.Allocated.equals(volume.getState())) {
+                if (volume != null && 
ImageFormat.QCOW2.equals(volume.getFormat()) && 
!Volume.State.Allocated.equals(volume.getState()) && 
!StoragePoolType.StorPool.equals(volume.getPoolType())) {
                     String message = "Unable to shrink volumes of type QCOW2";
                     logger.warn(message);
                     throw new InvalidParameterValueException(message);
diff --git a/test/integration/plugins/storpool/sp_util.py 
b/test/integration/plugins/storpool/sp_util.py
index eaea3d4934d..70f36609af5 100644
--- a/test/integration/plugins/storpool/sp_util.py
+++ b/test/integration/plugins/storpool/sp_util.py
@@ -79,6 +79,11 @@ class TestData():
     diskOfferingEncrypted2 = "diskOfferingEncrypted2"
     cephDiskOffering = "cephDiskOffering"
     nfsDiskOffering = "nfsDiskOffering"
+    diskOfferingTier1Tag = "diskOfferingTier1Tag"
+    diskOfferingTier2Tag = "diskOfferingTier2Tag"
+    diskOfferingTier1Template = "diskOfferingTier1Template"
+    diskOfferingTier2Template = "diskOfferingTier2Template"
+    diskOfferingWithTagsAndTempl = "diskOfferingWithTagsAndTempl"
     domainId = "domainId"
     hypervisor = "hypervisor"
     login = "login"
@@ -278,6 +283,46 @@ class TestData():
                 TestData.tags: "nfs",
                 "storagetype": "shared"
             },
+            TestData.diskOfferingTier1Template: {
+                "name": "tier1-template",
+                "displaytext": "Tier1 using different StorPool template",
+                "custom": True,
+                "hypervisorsnapshotreserve": 200,
+                TestData.tags: sp_template_1,
+                "storagetype": "shared"
+            },
+            TestData.diskOfferingTier2Template: {
+                "name": "tier2-template",
+                "displaytext": "Tier2 using different StorPool template",
+                "custom": True,
+                "hypervisorsnapshotreserve": 200,
+                TestData.tags: sp_template_1,
+                "storagetype": "shared"
+            },
+            TestData.diskOfferingTier1Tag: {
+                "name": "tier1-tag",
+                "displaytext": "Tier1 using QOS tags",
+                "custom": True,
+                "hypervisorsnapshotreserve": 200,
+                TestData.tags: sp_template_1,
+                "storagetype": "shared"
+            },
+            TestData.diskOfferingTier2Tag: {
+                "name": "tier2-tag",
+                "displaytext": "Tier2 using QOS tags",
+                "custom": True,
+                "hypervisorsnapshotreserve": 200,
+                TestData.tags: sp_template_1,
+                "storagetype": "shared"
+            },
+            TestData.diskOfferingWithTagsAndTempl: {
+                "name": "tier2-tag-template",
+                "displaytext": "Tier2 using QOS tags and template",
+                "custom": True,
+                "hypervisorsnapshotreserve": 200,
+                TestData.tags: sp_template_1,
+                "storagetype": "shared"
+            },
             TestData.volume_1: {
                 TestData.diskName: "test-volume-1",
             },
diff --git a/test/integration/plugins/storpool/test_storpool_tiers.py 
b/test/integration/plugins/storpool/test_storpool_tiers.py
new file mode 100644
index 00000000000..71758c24bed
--- /dev/null
+++ b/test/integration/plugins/storpool/test_storpool_tiers.py
@@ -0,0 +1,544 @@
+# 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.
+
+import pprint
+import uuid
+
+from marvin.cloudstackAPI import (listResourceDetails, addResourceDetail, 
changeOfferingForVolume)
+from marvin.cloudstackTestCase import cloudstackTestCase
+from marvin.codes import FAILED
+from marvin.lib.base import (DiskOffering,
+                             ServiceOffering,
+                             StoragePool,
+                             VirtualMachine,
+                             SecurityGroup,
+                             ResourceDetails
+                             )
+from marvin.lib.common import (get_domain,
+                               get_template,
+                               list_disk_offering,
+                               list_storage_pools,
+                               list_volumes,
+                               list_service_offering,
+                               list_zones)
+from marvin.lib.utils import random_gen, cleanup_resources
+from nose.plugins.attrib import attr
+from storpool import spapi
+
+from sp_util import (TestData, StorPoolHelper)
+
+
+class TestStorPoolTiers(cloudstackTestCase):
+    @classmethod
+    def setUpClass(cls):
+        super(TestStorPoolTiers, cls).setUpClass()
+        try:
+            cls.setUpCloudStack()
+        except Exception:
+            raise
+
+    @classmethod
+    def setUpCloudStack(cls):
+        config = cls.getClsConfig()
+        StorPoolHelper.logger = cls
+
+        zone = config.zones[0]
+        assert zone is not None
+
+        cls.spapi = spapi.Api(host=zone.spEndpoint, port=zone.spEndpointPort, 
auth=zone.spAuthToken, multiCluster=True)
+        testClient = super(TestStorPoolTiers, cls).getClsTestClient()
+        cls.apiclient = testClient.getApiClient()
+        cls.unsupportedHypervisor = False
+        cls.hypervisor = testClient.getHypervisorInfo()
+        if cls.hypervisor.lower() in ("hyperv", "lxc"):
+            cls.unsupportedHypervisor = True
+            return
+
+        cls._cleanup = []
+
+        cls.services = testClient.getParsedTestDataConfig()
+        # Get Zone, Domain and templates
+        cls.domain = get_domain(cls.apiclient)
+        cls.zone = list_zones(cls.apiclient, name=zone.name)[0]
+
+        td = TestData()
+        cls.testdata = td.testdata
+        cls.helper = StorPoolHelper()
+
+        disk_offerings_tier1_tags = cls.testdata[TestData.diskOfferingTier1Tag]
+        disk_offerings_tier2_tags = cls.testdata[TestData.diskOfferingTier2Tag]
+        disk_offerings_tier1_template = 
cls.testdata[TestData.diskOfferingTier1Template]
+        disk_offerings_tier2_template = 
cls.testdata[TestData.diskOfferingTier2Template]
+        disk_offerings_tier2_tags_template = 
cls.testdata[TestData.diskOfferingWithTagsAndTempl]
+
+        cls.qos = "SP_QOSCLASS"
+        cls.spTemplate = "SP_TEMPLATE"
+
+        cls.disk_offerings_tier1_tags = 
cls.getDiskOffering(disk_offerings_tier1_tags, cls.qos, "ssd")
+
+        cls.disk_offerings_tier2_tags = 
cls.getDiskOffering(disk_offerings_tier2_tags, cls.qos, "virtual")
+
+        cls.disk_offerings_tier1_template = 
cls.getDiskOffering(disk_offerings_tier1_template, cls.spTemplate, "ssd")
+
+        cls.disk_offerings_tier2_template = 
cls.getDiskOffering(disk_offerings_tier2_template, cls.spTemplate,
+                                                                "virtual")
+        cls.disk_offerings_tier2_tags_template = 
cls.getDiskOffering(disk_offerings_tier2_tags_template, cls.spTemplate,
+                                                                     "virtual")
+        cls.resourceDetails(cls.qos, 
cls.disk_offerings_tier2_tags_template.id, "virtual")
+
+        cls.account = cls.helper.create_account(
+            cls.apiclient,
+            cls.services["account"],
+            accounttype=1,
+            domainid=cls.domain.id,
+            roleid=1
+        )
+        cls._cleanup.append(cls.account)
+
+        securitygroup = SecurityGroup.list(cls.apiclient, 
account=cls.account.name, domainid=cls.account.domainid)[0]
+        cls.helper.set_securityGroups(cls.apiclient, account=cls.account.name, 
domainid=cls.account.domainid,
+                                      id=securitygroup.id)
+
+        storpool_primary_storage = cls.testdata[TestData.primaryStorage]
+
+        storpool_service_offerings = cls.testdata[TestData.serviceOffering]
+
+        cls.template_name = storpool_primary_storage.get("name")
+
+        storage_pool = list_storage_pools(
+            cls.apiclient,
+            name=cls.template_name
+        )
+
+        service_offerings = list_service_offering(
+            cls.apiclient,
+            name=cls.template_name
+        )
+
+        disk_offerings = list_disk_offering(
+            cls.apiclient,
+            name="ssd"
+        )
+
+        if storage_pool is None:
+            storage_pool = StoragePool.create(cls.apiclient, 
storpool_primary_storage)
+        else:
+            storage_pool = storage_pool[0]
+        cls.storage_pool = storage_pool
+        cls.debug(pprint.pformat(storage_pool))
+        if service_offerings is None:
+            service_offerings = ServiceOffering.create(cls.apiclient, 
storpool_service_offerings)
+        else:
+            service_offerings = service_offerings[0]
+        # The version of CentOS has to be supported
+        template = get_template(
+            cls.apiclient,
+            cls.zone.id,
+            account="system"
+        )
+
+        if template == FAILED:
+            assert False, "get_template() failed to return template\
+                    with description %s" % cls.services["ostype"]
+
+        cls.services["domainid"] = cls.domain.id
+        cls.services["small"]["zoneid"] = cls.zone.id
+        cls.services["templates"]["ostypeid"] = template.ostypeid
+        cls.services["zoneid"] = cls.zone.id
+
+        cls.service_offering = service_offerings
+        cls.debug(pprint.pformat(cls.service_offering))
+
+        cls.template = template
+        cls.random_data_0 = random_gen(size=100)
+        cls.test_dir = "/tmp"
+        cls.random_data = "random.data"
+        return
+
+    @classmethod
+    def getDiskOffering(cls, dataDiskOffering, qos, resValue):
+        disk_offerings = list_disk_offering(cls.apiclient, 
name=dataDiskOffering.get("name"))
+        if disk_offerings is None:
+            disk_offerings = DiskOffering.create(cls.apiclient, 
services=dataDiskOffering, custom=True)
+            cls.resourceDetails(qos, disk_offerings.id, resValue)
+        else:
+            disk_offerings = disk_offerings[0]
+            cls.resourceDetails(qos, disk_offerings.id, )
+        return disk_offerings
+
+    @classmethod
+    def tearDownClass(cls):
+        super(TestStorPoolTiers, cls).tearDownClass()
+
+    def setUp(self):
+        self.apiclient = self.testClient.getApiClient()
+        self.dbclient = self.testClient.getDbConnection()
+
+        if self.unsupportedHypervisor:
+            self.skipTest("Skipping test because unsupported hypervisor\
+                    %s" % self.hypervisor)
+        return
+
+    def tearDown(self):
+        super(TestStorPoolTiers, self).tearDown()
+
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
+    def test_01_check_tags_on_deployed_vm_and_datadisk(self):
+        virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag()
+        virtual_machine_tier1_tag.stop(self.apiclient, forced=True)
+
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
+    def test_02_change_offering_on_attached_root_disk(self):
+        virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag()
+
+        root_volume = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_tier1_tag.id, type="ROOT",
+                                   listall=True)
+        self.changeOfferingForVolume(root_volume[0].id, 
self.disk_offerings_tier2_tags.id, root_volume[0].size)
+        root_volume = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_tier1_tag.id, type="ROOT",
+                                   listall=True)
+        self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, 
qos_or_template=self.qos,
+                            
disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True)
+        virtual_machine_tier1_tag.stop(self.apiclient, forced=True)
+
+    def test_03_change_offering_on_attached_data_disk(self):
+        virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag()
+
+        root_volume = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_tier1_tag.id, type="DATADISK",
+                                   listall=True)
+        self.changeOfferingForVolume(root_volume[0].id, 
self.disk_offerings_tier2_tags.id, root_volume[0].size)
+        root_volume = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_tier1_tag.id, type="DATADISK",
+                                   listall=True)
+        self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, 
qos_or_template=self.qos,
+                            
disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True)
+        virtual_machine_tier1_tag.stop(self.apiclient, forced=True)
+
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
+    def test_04_check_templates_on_deployed_vm_and_datadisk(self):
+        virtual_machine_template_tier1 = VirtualMachine.create(
+            self.apiclient,
+            {"name": "StorPool-%s" % uuid.uuid4()},
+            zoneid=self.zone.id,
+            templateid=self.template.id,
+            accountid=self.account.name,
+            domainid=self.account.domainid,
+            serviceofferingid=self.service_offering.id,
+            overridediskofferingid=self.disk_offerings_tier1_template.id,
+            diskofferingid=self.disk_offerings_tier1_template.id,
+            size=2,
+            hypervisor=self.hypervisor,
+            rootdisksize=10
+        )
+        volumes = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_template_tier1.id, listall=True)
+        for v in volumes:
+            self.check_storpool_template(v, 
self.disk_offerings_tier1_template.id, self.spTemplate)
+        virtual_machine_template_tier1.stop(self.apiclient, forced=True)
+
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
+    def test_05_check_templates_on_deployed_vm_and_datadisk_tier2(self):
+        virtual_machine_template_tier2 = VirtualMachine.create(
+            self.apiclient,
+            {"name": "StorPool-%s" % uuid.uuid4()},
+            zoneid=self.zone.id,
+            templateid=self.template.id,
+            accountid=self.account.name,
+            domainid=self.account.domainid,
+            serviceofferingid=self.service_offering.id,
+            overridediskofferingid=self.disk_offerings_tier2_template.id,
+            diskofferingid=self.disk_offerings_tier2_template.id,
+            size=2,
+            hypervisor=self.hypervisor,
+            rootdisksize=10
+        )
+        volumes = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_template_tier2.id, listall=True)
+        for v in volumes:
+            self.check_storpool_template(v, 
self.disk_offerings_tier2_template.id, self.spTemplate)
+        virtual_machine_template_tier2.stop(self.apiclient, forced=True)
+
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
+    def test_06_change_offerings_with_tags_detached_volume(self):
+        disk_off_id = self.disk_offerings_tier2_tags.id
+        virtual_machine_tier2_tag = VirtualMachine.create(
+            self.apiclient,
+            {"name": "StorPool-%s" % uuid.uuid4()},
+            zoneid=self.zone.id,
+            templateid=self.template.id,
+            accountid=self.account.name,
+            domainid=self.account.domainid,
+            serviceofferingid=self.service_offering.id,
+            overridediskofferingid=disk_off_id,
+            diskofferingid=disk_off_id,
+            size=2,
+            hypervisor=self.hypervisor,
+            rootdisksize=10
+        )
+        virtual_machine_tier2_tag.stop(self.apiclient, forced=True)
+        volumes = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_tier2_tag.id, type="DATADISK",
+                               listall=True)
+
+        virtual_machine_tier2_tag.detach_volume(
+            self.apiclient,
+            volumes[0]
+        )
+
+        self.vc_policy_tags(volumes=volumes, vm=virtual_machine_tier2_tag, 
qos_or_template=self.qos,
+                            disk_offering_id=disk_off_id, attached=True)
+
+        self.changeOfferingForVolume(volumes[0].id, 
self.disk_offerings_tier1_tags.id, volumes[0].size)
+        self.vc_policy_tags(volumes=volumes, vm=virtual_machine_tier2_tag, 
qos_or_template=self.qos,
+                            
disk_offering_id=self.disk_offerings_tier1_tags.id, attached=True)
+
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
+    def test_07_change_offerings_with_template_detached_volume(self):
+        disk_off_id = self.disk_offerings_tier2_template.id
+        virtual_machine_tier2_template = VirtualMachine.create(
+            self.apiclient,
+            {"name": "StorPool-%s" % uuid.uuid4()},
+            zoneid=self.zone.id,
+            templateid=self.template.id,
+            accountid=self.account.name,
+            domainid=self.account.domainid,
+            serviceofferingid=self.service_offering.id,
+            overridediskofferingid=disk_off_id,
+            diskofferingid=disk_off_id,
+            size=2,
+            hypervisor=self.hypervisor,
+            rootdisksize=10
+        )
+        virtual_machine_tier2_template.stop(self.apiclient, forced=True)
+        volumes = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_tier2_template.id, type="DATADISK",
+                               listall=True)
+
+        virtual_machine_tier2_template.detach_volume(
+            self.apiclient,
+            volumes[0]
+        )
+
+        self.check_storpool_template(volume=volumes[0], 
disk_offering_id=disk_off_id, qos_or_template=self.spTemplate)
+
+        self.changeOfferingForVolume(volumes[0].id, 
self.disk_offerings_tier1_template.id, volumes[0].size)
+        self.check_storpool_template(volume=volumes[0], 
disk_offering_id=self.disk_offerings_tier1_template.id,
+                                     qos_or_template=self.spTemplate)
+
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
+    def test_08_deploy_vm_with_tags_and_template_in_offerings(self):
+        """
+            Deploy virtual machine with disk offering on which resource 
details is set tier2 template and tier2 qos tags
+        """
+        disk_off_id = self.disk_offerings_tier2_tags_template.id
+        virtual_machine_tier2_template = VirtualMachine.create(
+            self.apiclient,
+            {"name": "StorPool-%s" % uuid.uuid4()},
+            zoneid=self.zone.id,
+            templateid=self.template.id,
+            accountid=self.account.name,
+            domainid=self.account.domainid,
+            serviceofferingid=self.service_offering.id,
+            overridediskofferingid=disk_off_id,
+            diskofferingid=disk_off_id,
+            size=2,
+            hypervisor=self.hypervisor,
+            rootdisksize=10
+        )
+        virtual_machine_tier2_template.stop(self.apiclient, forced=True)
+        volumes = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_tier2_template.id, type="DATADISK",
+                               listall=True)
+
+        virtual_machine_tier2_template.detach_volume(
+            self.apiclient,
+            volumes[0]
+        )
+
+        self.check_storpool_template(volume=volumes[0], 
disk_offering_id=disk_off_id, qos_or_template=self.spTemplate,
+                                     diff_template=True)
+        self.vc_policy_tags(volumes=volumes, 
vm=virtual_machine_tier2_template, qos_or_template=self.qos,
+                            disk_offering_id=disk_off_id, attached=True)
+
+        self.changeOfferingForVolume(volumes[0].id, 
self.disk_offerings_tier1_tags.id, volumes[0].size)
+        self.vc_policy_tags(volumes=volumes, 
vm=virtual_machine_tier2_template, qos_or_template=self.qos,
+                            
disk_offering_id=self.disk_offerings_tier1_tags.id, attached=True)
+
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
+    def test_09_resize_root_volume(self):
+        '''
+        Resize Root volume with changeOfferingForVolume
+        '''
+        virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag()
+
+        root_volume = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_tier1_tag.id, type="ROOT",
+                                   listall=True)
+        self.changeOfferingForVolume(root_volume[0].id, 
self.disk_offerings_tier2_tags.id, (root_volume[0].size + 1024))
+        root_volume = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_tier1_tag.id, type="ROOT",
+                                   listall=True)
+        self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, 
qos_or_template=self.qos,
+                            
disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True)
+        virtual_machine_tier1_tag.stop(self.apiclient, forced=True)
+
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
+    def test_10_shrink_root_volume(self):
+        '''
+        Shrink Root volume with changeOfferingForVolume
+        '''
+        virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag()
+
+        root_volume = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_tier1_tag.id, type="ROOT",
+                                   listall=True)
+        virtual_machine_tier1_tag.stop(self.apiclient, forced=True)
+        self.changeOfferingForVolume(root_volume[0].id, 
self.disk_offerings_tier2_tags.id, (root_volume[0].size - 1024),
+                                     True)
+        root_volume = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_tier1_tag.id, type="ROOT",
+                                   listall=True)
+        self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, 
qos_or_template=self.qos,
+                            
disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True)
+
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
+    def test_11_resize_data_volume(self):
+        '''
+        Resize DATADISK volume with changeOfferingForVolume
+        '''
+        virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag()
+
+        root_volume = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_tier1_tag.id, type="DATADISK",
+                                   listall=True)
+        self.changeOfferingForVolume(root_volume[0].id, 
self.disk_offerings_tier2_tags.id, (root_volume[0].size + 1024))
+        root_volume = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_tier1_tag.id, type="DATADISK",
+                                   listall=True)
+        self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, 
qos_or_template=self.qos,
+                            
disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True)
+        virtual_machine_tier1_tag.stop(self.apiclient, forced=True)
+
+    @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true")
+    def test_12_shrink_data_volume(self):
+        '''
+        Shrink DATADISK volume with changeOfferingForVolume
+        '''
+        virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag()
+
+        root_volume = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_tier1_tag.id, type="DATADISK",
+                                   listall=True)
+        self.changeOfferingForVolume(root_volume[0].id, 
self.disk_offerings_tier2_tags.id, (root_volume[0].size - 1024),
+                                     True)
+        root_volume = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_tier1_tag.id, type="DATADISK",
+                                   listall=True)
+        self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, 
qos_or_template=self.qos,
+                            
disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True)
+        virtual_machine_tier1_tag.stop(self.apiclient, forced=True)
+
+    def deploy_vm_and_check_tier_tag(self):
+        virtual_machine_tier1_tag = VirtualMachine.create(
+            self.apiclient,
+            {"name": "StorPool-%s" % uuid.uuid4()},
+            zoneid=self.zone.id,
+            templateid=self.template.id,
+            accountid=self.account.name,
+            domainid=self.account.domainid,
+            serviceofferingid=self.service_offering.id,
+            overridediskofferingid=self.disk_offerings_tier1_tags.id,
+            diskofferingid=self.disk_offerings_tier1_tags.id,
+            size=2,
+            hypervisor=self.hypervisor,
+            rootdisksize=10
+        )
+        volumes = list_volumes(self.apiclient, 
virtualmachineid=virtual_machine_tier1_tag.id, listall=True)
+        self.vc_policy_tags(volumes=volumes, vm=virtual_machine_tier1_tag, 
qos_or_template=self.qos,
+                            
disk_offering_id=self.disk_offerings_tier1_tags.id, attached=True)
+        return virtual_machine_tier1_tag
+
+    @classmethod
+    def resourceDetails(cls, qos, id, resValue=None):
+        listResourceDetailCmd = listResourceDetails.listResourceDetailsCmd()
+        listResourceDetailCmd.resourceid = id
+        listResourceDetailCmd.resourcetype = "DiskOffering"
+        listResourceDetailCmd.key = qos
+        details = cls.apiclient.listResourceDetails(listResourceDetailCmd)
+
+        if details is None:
+            resource = addResourceDetail.addResourceDetailCmd()
+            resource.resourceid = id
+            resource.resourcetype = "DiskOffering"
+            resDet = {'key': qos, 'value': resValue}
+            resource.details = [resDet]
+
+            resource.fordisplay = True
+            details = cls.apiclient.addResourceDetail(resource)
+
+    @classmethod
+    def getZone(cls):
+        zones = list_zones(cls.apiclient)
+        for z in zones:
+            if z.name == cls.getClsConfig().mgtSvr[0].zone:
+                cls.zone = z
+        assert cls.zone is not None
+
+    def vc_policy_tags(self, volumes, vm, qos_or_template, disk_offering_id, 
should_tags_exists=None, vm_tags=None,
+                       attached=None):
+        vc_policy_tag = False
+        cvm_tag = False
+        qs_tag = False
+        id = vm.id
+        for v in volumes:
+            name = v.path.split("/")[3]
+            volume = self.spapi.volumeList(volumeName="~" + name)
+            tags = volume[0].tags
+            resource_details_value = ResourceDetails.list(self.apiclient, 
resourcetype="DiskOffering",
+                                                          
resourceid=disk_offering_id, key=qos_or_template)
+            for t in tags:
+                self.debug("TAGS are %s" % t)
+                if vm_tags:
+                    for vm_tag in vm_tags:
+                        if t == vm_tag.key:
+                            vc_policy_tag = True
+                            self.assertEqual(tags[t], vm_tag.value, "Tags are 
not equal")
+                if t == 'cvm':
+                    self.debug("CVM tag %s is not the same as vm UUID %s" % 
(tags[t], id))
+                    self.debug(type(tags[t]))
+                    self.debug(len(tags[t]))
+                    self.debug(type(id))
+                    self.debug(len(id))
+                    cvm_tag = True
+                    self.assertEqual(tags[t], id, "CVM tag is not the same as 
vm UUID ")
+                if t == 'qc':
+                    qs_tag = True
+                    self.assertEqual(tags[t], resource_details_value[0].value, 
"QOS tags should be the same")
+        if should_tags_exists:
+            self.assertTrue(vc_policy_tag, "There aren't volumes with vm tags")
+            self.assertTrue(cvm_tag, "There aren't volumes with vm tags")
+        if attached:
+            self.assertTrue(qs_tag, "The QOS tag isn't set")
+        else:
+            self.assertFalse(vc_policy_tag, "The tags should be removed")
+            self.assertFalse(cvm_tag, "The tags should be removed")
+
+    def check_storpool_template(self, volume, disk_offering_id, 
qos_or_template, diff_template=None):
+        name = volume.path.split("/")[3]
+        sp_volume = self.spapi.volumeList(volumeName="~" + name)
+        template = sp_volume[0].templateName
+        resource_details_value = ResourceDetails.list(self.apiclient, 
resourcetype="DiskOffering",
+                                                      
resourceid=disk_offering_id, key=qos_or_template)
+        if diff_template:
+            self.assertNotEqual(template, resource_details_value[0].value, 
"The templates should not be the same")
+        else:
+            self.assertEqual(template, resource_details_value[0].value)
+
+    def changeOfferingForVolume(self, volume_id, disk_offering_id, size, 
shrinkok=None):
+        size = int(size / 1024 / 1024 / 1024)
+        change_offering_for_volume_cmd = 
changeOfferingForVolume.changeOfferingForVolumeCmd()
+        change_offering_for_volume_cmd.id = volume_id
+        change_offering_for_volume_cmd.diskofferingid = disk_offering_id
+        change_offering_for_volume_cmd.size = size
+        change_offering_for_volume_cmd.shrinkok = shrinkok
+
+        return 
self.apiclient.changeOfferingForVolume(change_offering_for_volume_cmd)
diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py
index 9c69d33a4ca..7e06ededa1d 100755
--- a/tools/marvin/marvin/lib/base.py
+++ b/tools/marvin/marvin/lib/base.py
@@ -527,7 +527,7 @@ class VirtualMachine:
                customcpuspeed=None, custommemory=None, rootdisksize=None,
                rootdiskcontroller=None, vpcid=None, macaddress=None, 
datadisktemplate_diskoffering_list={},
                properties=None, nicnetworklist=None, bootmode=None, 
boottype=None, dynamicscalingenabled=None,
-               userdataid=None, userdatadetails=None, extraconfig=None, 
size=None):
+               userdataid=None, userdatadetails=None, extraconfig=None, 
size=None, overridediskofferingid=None):
         """Create the instance"""
 
         cmd = deployVirtualMachine.deployVirtualMachineCmd()
@@ -537,6 +537,9 @@ class VirtualMachine:
         elif "serviceoffering" in services:
             cmd.serviceofferingid = services["serviceoffering"]
 
+        if overridediskofferingid:
+            cmd.overridediskofferingid = overridediskofferingid
+
         if zoneid:
             cmd.zoneid = zoneid
         elif "zoneid" in services:

Reply via email to