@@ -1245,6 +1245,28 @@ def _root_disk_offering(cls, storage_tag):
12451245 cls ._cleanup .insert (0 , offering )
12461246 return offering
12471247
1248+ def _qos_disk_offering (self , disksize , miniops , maxiops , storage_tag = None ):
1249+ """Create a disk offering that carries fixed min and max IOPS."""
1250+ data = {
1251+ "name" : "ontap-migration-qos-%d-%d-%d" % (
1252+ miniops , maxiops , random .randint (0 , 999999 )
1253+ ),
1254+ "displaytext" : "ONTAP migration QoS disk" ,
1255+ "disksize" : disksize ,
1256+ "miniops" : miniops ,
1257+ "maxiops" : maxiops ,
1258+ }
1259+ if storage_tag :
1260+ data ["tags" ] = storage_tag
1261+ offering = DiskOffering .create (self .apiClient , data )
1262+ self .__class__ ._cleanup .insert (0 , offering )
1263+ return offering
1264+
1265+ def _qos_policy_name (self , miniops , maxiops ):
1266+ """Return the ONTAP policy name the driver builds for these IOPS."""
1267+ svm = str (self .__class__ .svm_name ).replace ("." , "_" )
1268+ return "cs_%d_to_%d_iops_%s" % (miniops , maxiops , svm )
1269+
12481270 @classmethod
12491271 def _create_ontap_pool (cls , protocol , scope , cluster_id = None ):
12501272 config = cls .config_data
@@ -1324,9 +1346,11 @@ def _create_ontap_pool(cls, protocol, scope, cluster_id=None):
13241346 )
13251347 return pool
13261348
1327- def _deploy_vm (self , storage_tag = None , host_id = None ):
1349+ def _deploy_vm (self , storage_tag = None , host_id = None , root_offering = None ):
13281350 offering = self ._service_offering ()
1329- root_disk_offering = self ._root_disk_offering (storage_tag )
1351+ root_disk_offering = root_offering or self ._root_disk_offering (
1352+ storage_tag
1353+ )
13301354 cmd = deployVirtualMachineAPI .deployVirtualMachineCmd ()
13311355 cmd .zoneid = self .__class__ .zone .id
13321356 cmd .templateid = self .__class__ .template_id
@@ -1453,9 +1477,12 @@ def _require_secondary_cluster(cls):
14531477 )
14541478 return cls .secondary_cluster_id
14551479
1456- def _create_data_volume (self , pool ):
1480+ def _create_data_volume (self , pool , offering = None ):
14571481 cls = self .__class__
1458- offering_id = getattr (cls , "small_disk_offering_id" , None )
1482+ if offering is not None :
1483+ offering_id = offering .id
1484+ else :
1485+ offering_id = getattr (cls , "small_disk_offering_id" , None )
14591486 if offering_id is None :
14601487 offering = DiskOffering .create (cls .apiClient , {
14611488 "name" : "ontap-migration-data-%d" % random .randint (0 , 999999 ),
@@ -1475,6 +1502,67 @@ def _create_data_volume(self, pool):
14751502 cls .created_volumes .append (volume )
14761503 return volume
14771504
1505+ def _assert_volume_qos (self , volume , protocol , pool , miniops , maxiops ):
1506+ """Check CloudStack IOPS and the destination ONTAP QoS policy."""
1507+ current = self ._get_volume (volume .id )
1508+ self .assertEqual (int (current .miniops ), miniops )
1509+ self .assertEqual (int (current .maxiops ), maxiops )
1510+ expected = self ._qos_policy_name (miniops , maxiops )
1511+ if protocol .upper () == "NFS3" :
1512+ record = self .__class__ .ontap .get_nfs_file (
1513+ pool .name , current .path
1514+ )
1515+ else :
1516+ record = self .__class__ .ontap .get_lun (
1517+ self .__class__ .svm_name ,
1518+ self ._backend_name ("ISCSI" , pool , current ),
1519+ )
1520+ policy = (record or {}).get ("qos_policy" ) or {}
1521+ self .assertEqual (
1522+ policy .get ("name" ),
1523+ expected ,
1524+ "Volume %s on %s has QoS %s, expected %s"
1525+ % (current .id , pool .name , policy .get ("name" ), expected ),
1526+ )
1527+ return current
1528+
1529+ def _assert_vm_qos (self , vm , protocol , pool , root_iops , data_iops ):
1530+ """Check root and data disks after a VM storage migration."""
1531+ volumes = self ._vm_volumes (vm .id )
1532+ roots = [
1533+ volume for volume in volumes
1534+ if str (getattr (volume , "type" , "" )).upper () == "ROOT"
1535+ ]
1536+ data = [
1537+ volume for volume in volumes
1538+ if str (getattr (volume , "type" , "" )).upper () != "ROOT"
1539+ ]
1540+ self .assertEqual (len (roots ), 1 )
1541+ self .assertTrue (data )
1542+ self ._assert_volume_qos (
1543+ roots [0 ], protocol , pool , root_iops [0 ], root_iops [1 ]
1544+ )
1545+ for volume in data :
1546+ self ._assert_volume_qos (
1547+ volume , protocol , pool , data_iops [0 ], data_iops [1 ]
1548+ )
1549+
1550+ def _prepare_qos_vm (self , root_iops , data_iops , stop = False ):
1551+ """Deploy a DefaultPrimary VM whose root and data disks have IOPS."""
1552+ host = self ._host_for_cluster (self .__class__ .cluster .id )
1553+ root = self ._qos_disk_offering (
1554+ 8 , root_iops [0 ], root_iops [1 ], self ._default_storage_tag ()
1555+ )
1556+ vm = self ._deploy_vm (host_id = host .id , root_offering = root )
1557+ data = self ._create_data_volume (
1558+ self .__class__ .default_pool ,
1559+ self ._qos_disk_offering (1 , data_iops [0 ], data_iops [1 ]),
1560+ )
1561+ self ._attach_volume (vm , data )
1562+ if stop :
1563+ self ._stop_vm (vm )
1564+ return self ._get_vm (vm .id )
1565+
14781566 def _attach_volume (self , vm , volume ):
14791567 cmd = attachVolumeAPI .attachVolumeCmd ()
14801568 cmd .id = volume .id
0 commit comments