From 2b4b926e47b2a1f1fef630c3e15a57e85c714638 Mon Sep 17 00:00:00 2001 From: sinhaashish Date: Mon, 17 Jun 2024 03:51:38 +0000 Subject: [PATCH] feat(topology): add test for pool affinity Signed-off-by: sinhaashish --- .../volume/topology/pool-topology.feature | 52 ++++- .../volume/topology/test_node_topology.py | 1 + .../volume/topology/test_pool_topology.py | 220 ++++++++++++++---- 3 files changed, 222 insertions(+), 51 deletions(-) diff --git a/tests/bdd/features/volume/topology/pool-topology.feature b/tests/bdd/features/volume/topology/pool-topology.feature index eaa0b92e8..f3d9754b6 100644 --- a/tests/bdd/features/volume/topology/pool-topology.feature +++ b/tests/bdd/features/volume/topology/pool-topology.feature @@ -14,25 +14,55 @@ Feature: Volume Pool Topology which signifies the volume pool topology inclusion labels as {"rack": "", "zone" : ""} Background: - Given a control plane, three Io-Engine instances, nine pools + Given a control plane, three Io-Engine instances, fourteen pools # The labels to be applied to the pools. ############################################################################################### -# Description || Pool Name || Label || Node || +# Description || Pool Name || Label || Node || #============================================================================================== -# "pool1" has || node1pool1 || zone-us=us-west-1 || io-engine-1 || -# the label || node2pool1 || zone-us=us-west-1 || io-engine-2 || -# "zone-us=us-west-1" || node3pool1 || zone-us=us-west-1 || io-engine-3 || +# "pool1" has || node1pool1 || zone-us=us-west-1 || io-engine-1 || +# the label || node2pool1 || zone-us=us-west-1 || io-engine-2 || +# "zone-us=us-west-1" || node3pool1 || zone-us=us-west-1 || io-engine-3 || #============================================================================================== -# "pool2" has || node1pool2 || zone-ap=ap-south-1 || io-engine-1 || -# the label || node2pool2 || zone-ap=ap-south-1 || io-engine-2 || -# "zone-ap=ap-south-1" || node3pool2 || zone-ap=ap-south-1 || io-engine-3 || +# "pool2" has || node1pool2 || zone-ap=ap-south-1 || io-engine-1 || +# the label || node2pool2 || zone-ap=ap-south-1 || io-engine-2 || +# "zone-ap=ap-south-1" || node3pool2 || zone-ap=ap-south-1 || io-engine-3 || #============================================================================================== -# "pool3" has || node1pool3 || zone-eu=eu-west-3 || io-engine-1 || -# the label || node2pool3 || zone-eu=eu-west-3 || io-engine-2 || -# "zone-eu=eu-west-3" || node3pool3 || zone-eu=eu-west-3 || io-engine-3 || +# "pool3" has || node1pool3 || zone-eu=eu-west-3 || io-engine-1 || +# the label || node2pool3 || zone-eu=eu-west-3 || io-engine-2 || +# "zone-eu=eu-west-3" || node3pool3 || zone-eu=eu-west-3 || io-engine-3 || #============================================================================================== +# "pool4" has || node1pool4 || zone-ca=ca-central-1 || io-engine-1 || +# the label || node2pool4 || zone-ca=ca-central-1 || io-engine-2 || +# "zone-ca=ca-central-1" || node3pool4 || zone-ca=ca-central-1 || io-engine-3 || +#============================================================================================== +# "pool5" has || node1pool5 || zone-ca=ca-west-1 || io-engine-1 || +# the label || node2pool5 || zone-ca=ca-west-1 || io-engine-2 || +# "zone-ca=ca-west-1" || || || || +#============================================================================================== + + Scenario Outline: Suitable pools which contain volume topology affinity key + Given a request for a replica volume with poolAffinityTopologyKey as and pool topology affinity as + When the desired number of replica of volume i.e. here; is number of the pools containing the label + Then the replica volume creation should and provisioned on pools with labels + Examples: + | pool_affinity_topology_key | volume_pool_topology_affinty | replica | expression | result | provisioned | pool_label | + | True | zone-us | 1 | <= | succeed | must be | zone-us=us-west-1 | + | True | zone-us | 2 | <= | succeed | must be | zone-us=us-west-1 | + | True | zone-us | 3 | <= | succeed | must be | zone-us=us-west-1 | + | True | zone-us | 4 | > | fail | not | zone-us=us-west-1 | + | True | zone-ap | 1 | <= | succeed | must be | zone-ap=ap-south-1 | + | True | zone-ap | 2 | <= | succeed | must be | zone-ap=ap-south-1 | + | True | zone-ap | 3 | <= | succeed | must be | zone-ap=ap-south-1 | + | True | zone-ap | 4 | > | fail | not | zone-ap=ap-south-1 | + | True | zone-eu | 1 | <= | succeed | must be | zone-eu=eu-west-3 | + | True | zone-eu | 2 | <= | succeed | must be | zone-eu=eu-west-3 | + | True | zone-eu | 3 | <= | succeed | must be | zone-eu=eu-west-3 | + | True | zone-eu | 4 | > | fail | not | zone-eu=eu-west-3 | + | True | zone-ca | 1 | <= | succeed | must be | zone-ca=ca-central-1 | + | True | zone-ca | 2 | <= | succeed | must be | zone-ca=ca-central-1 | + | True | zone-ca | 3 | <= | succeed | must be | zone-ca=ca-central-1 | Scenario Outline: Suitable pools which contain volume topology labels Given a request for a replica volume with poolAffinityTopologyLabel as and pool topology inclusion as diff --git a/tests/bdd/features/volume/topology/test_node_topology.py b/tests/bdd/features/volume/topology/test_node_topology.py index 345931576..41c904301 100644 --- a/tests/bdd/features/volume/topology/test_node_topology.py +++ b/tests/bdd/features/volume/topology/test_node_topology.py @@ -566,6 +566,7 @@ def create_volume_body( inclusion={ **inclusion_labels, }, + affinitykey=[], ) ) ) diff --git a/tests/bdd/features/volume/topology/test_pool_topology.py b/tests/bdd/features/volume/topology/test_pool_topology.py index 6c075000e..b3d4c4fad 100644 --- a/tests/bdd/features/volume/topology/test_pool_topology.py +++ b/tests/bdd/features/volume/topology/test_pool_topology.py @@ -5,13 +5,14 @@ import pytest import docker import requests -import time +import time from common import prod_domain_name, disk_pool_label_key, disk_pool_label_val from common.deployer import Deployer from common.apiclient import ApiClient from common.docker import Docker from common.nvme import nvme_connect, nvme_disconnect +from time import sleep from common.fio import Fio from common.operations import Cluster @@ -28,15 +29,28 @@ NODE_1_NAME = "io-engine-1" NODE_2_NAME = "io-engine-2" NODE_3_NAME = "io-engine-3" + +# The UUIDs of the pools on node1 NODE_1_POOL_1_UUID = "node1pool1" NODE_1_POOL_2_UUID = "node1pool2" NODE_1_POOL_3_UUID = "node1pool3" +NODE_1_POOL_4_UUID = "node1pool4" +NODE_1_POOL_5_UUID = "node1pool5" + +# The UUIDs of the pools on node2 NODE_2_POOL_1_UUID = "node2pool1" NODE_2_POOL_2_UUID = "node2pool2" NODE_2_POOL_3_UUID = "node2pool3" +NODE_2_POOL_4_UUID = "node2pool4" +NODE_2_POOL_5_UUID = "node2pool5" + +# The UUIDs of the pools on node3 NODE_3_POOL_1_UUID = "node3pool1" NODE_3_POOL_2_UUID = "node3pool2" NODE_3_POOL_3_UUID = "node3pool3" +NODE_3_POOL_4_UUID = "node3pool4" + +# The key used to pass the volume create request between test steps. CREATE_REQUEST_KEY = "create_request" VOLUME_SIZE = 10485761 VOLUME_UUID = "5cd5378e-3f05-47f1-a830-a0f5873a1441" @@ -46,21 +60,28 @@ # The labels to be applied to the pools. ############################################################################################### -# Description || Pool Name || Label || Node || -# ============================================================================================== -# "pool1" has || node1pool1 || zone-us=us-west-1 || io-engine-1 || -# the label || node2pool1 || zone-us=us-west-1 || io-engine-2 || -# "zone-us=us-west-1" || node3pool1 || zone-us=us-west-1 || io-engine-3 || -# ============================================================================================== -# "pool2" has || node1pool2 || zone-ap=ap-south-1 || io-engine-1 || -# the label || node2pool2 || zone-ap=ap-south-1 || io-engine-2 || -# "zone-ap=ap-south-1" || node3pool2 || zone-ap=ap-south-1 || io-engine-3 || -# ============================================================================================== -# "pool3" has || node1pool3 || zone-eu=eu-west-3 || io-engine-1 || -# the label || node2pool3 || zone-eu=eu-west-3 || io-engine-2 || -# "zone-eu=eu-west-3" || node3pool3 || zone-eu=eu-west-3 || io-engine-3 || -# ============================================================================================== -# ========================================================================================= +# Description || Pool Name || Label || Node || +#============================================================================================== +# "pool1" has || node1pool1 || zone-us=us-west-1 || io-engine-1 || +# the label || node2pool1 || zone-us=us-west-1 || io-engine-2 || +# "zone-us=us-west-1" || node3pool1 || zone-us=us-west-1 || io-engine-3 || +#============================================================================================== +# "pool2" has || node1pool2 || zone-ap=ap-south-1 || io-engine-1 || +# the label || node2pool2 || zone-ap=ap-south-1 || io-engine-2 || +# "zone-ap=ap-south-1" || node3pool2 || zone-ap=ap-south-1 || io-engine-3 || +#============================================================================================== +# "pool3" has || node1pool3 || zone-eu=eu-west-3 || io-engine-1 || +# the label || node2pool3 || zone-eu=eu-west-3 || io-engine-2 || +# "zone-eu=eu-west-3" || node3pool3 || zone-eu=eu-west-3 || io-engine-3 || +#============================================================================================== +# "pool4" has || node1pool4 || zone-ca=ca-central-1 || io-engine-1 || +# the label || node2pool4 || zone-ca=ca-central-1 || io-engine-2 || +# "zone-ca=ca-central-1" || node3pool4 || zone-ca=ca-central-1 || io-engine-3 || +#============================================================================================== +# "pool5" has || node1pool5 || zone-ca=ca-west-1 || io-engine-1 || +# the label || node2pool5 || zone-ca=ca-west-1 || io-engine-2 || +# "zone-ca=ca-west-1" || || || || +#============================================================================================== POOL_CONFIGURATIONS = [ # Pool node1pool1 has the label "zone-us=us-west-1" and is on node "io-engine-1" @@ -180,12 +201,79 @@ }, ), }, + # Pool node1pool4 has the label "zone-ca=ca-central-1" and is on node "io-engine-1" + { + "node_name": NODE_1_NAME, + "pool_uuid": NODE_1_POOL_4_UUID, + "pool_body": CreatePoolBody( + ["malloc:///disk4?size_mb=50"], + labels={ + DISKPOOL_LABEL_KEY: DISKPOOL_LABEL_VAL, + "node": "io-engine-1", + "zone-ca": "ca-central-1", + }, + ), + }, + # Pool node2pool4 has the label "zone-ca=ca-central-1" and is on node "io-engine-2" + { + "node_name": NODE_2_NAME, + "pool_uuid": NODE_2_POOL_4_UUID, + "pool_body": CreatePoolBody( + ["malloc:///disk4?size_mb=50"], + labels={ + DISKPOOL_LABEL_KEY: DISKPOOL_LABEL_VAL, + "node": "io-engine-2", + "zone-ca": "ca-central-1", + }, + ), + }, + # Pool node3pool4 has the label "zone-ca=ca-central-1" and is on node "io-engine-3" + { + "node_name": NODE_3_NAME, + "pool_uuid": NODE_3_POOL_4_UUID, + "pool_body": CreatePoolBody( + ["malloc:///disk4?size_mb=50"], + labels={ + DISKPOOL_LABEL_KEY: DISKPOOL_LABEL_VAL, + "node": "io-engine-3", + "zone-ca": "ca-central-1", + }, + ), + }, + # Pool node1pool5 has the label "zone-ca=ca-west-1" and is on node "io-engine-1" + { + "node_name": NODE_1_NAME, + "pool_uuid": NODE_1_POOL_5_UUID, + "pool_body": CreatePoolBody( + ["malloc:///disk5?size_mb=50"], + labels={ + DISKPOOL_LABEL_KEY: DISKPOOL_LABEL_VAL, + "node": "io-engine-1", + "zone-ca": "ca-west-1", + }, + ), + }, + # Pool node2pool5 has the label "zone-ca=ca-west-1" and is on node "io-engine-2" + { + "node_name": NODE_2_NAME, + "pool_uuid": NODE_2_POOL_5_UUID, + "pool_body": CreatePoolBody( + ["malloc:///disk5?size_mb=50"], + labels={ + DISKPOOL_LABEL_KEY: DISKPOOL_LABEL_VAL, + "node": "io-engine-2", + "zone-ca": "ca-west-1", + }, + ), + }, + ] @pytest.fixture(scope="module") def init(): Deployer.start(NUM_IO_ENGINES, io_engine_coreisol=True) + # Create the pools. for config in POOL_CONFIGURATIONS: ApiClient.pools_api().put_node_pool( @@ -216,17 +304,20 @@ def replica_ctx(): def test_suitable_pools_which_contain_volume_topology_labels(): """Suitable pools which contain volume topology labels.""" - @scenario( "pool-topology.feature", "Suitable pools which contain volume topology keys only" ) def test_suitable_pools_which_contain_volume_topology_keys_only(): """Suitable pools which contain volume topology keys only.""" +@scenario('pool-topology.feature', 'Suitable pools which contain volume topology affinity key') +def test_suitable_pools_which_contain_volume_topology_affinity_key(): + """Suitable pools which contain volume topology affinity key.""" -@given("a control plane, three Io-Engine instances, nine pools") -def a_control_plane_three_ioengine_instances_nine_pools(init): - """a control plane, three Io-Engine instances, nine pools.""" + +@given("a control plane, three Io-Engine instances, fourteen pools") +def a_control_plane_three_ioengine_instances_fourteen_pools(init): + """a control plane, three Io-Engine instances, fourteen pools.""" docker_client = docker.from_env() # The control plane comprises the core agents, rest server and etcd instance. @@ -247,10 +338,23 @@ def a_control_plane_three_ioengine_instances_nine_pools(init): # Check for a pools pools = ApiClient.pools_api().get_pools() - assert len(pools) == 9 + assert len(pools) == 14 yield Cluster.cleanup(pools=False) +@given( + parsers.parse( + "a request for a {replica} replica volume with poolAffinityTopologyKey as {pool_affinity_topology_key} and pool topology affinity as {volume_pool_topology_affinty}" + ) +) +def a_request_for_a_replica_replica_volume_with_poolaffinitytopologykey_as_pool_affinity_topology_key_and_pool_topology_affinity_as_volume_pool_topology_affinty( + create_request, replica, pool_affinity_topology_key, volume_pool_topology_affinty +): + """a request for a replica volume with poolAffinityTopologyKey as and pool topology affinity as .""" + if pool_affinity_topology_key == "True": + request = create_volume_body(replica, volume_pool_topology_affinty, pool_affinity_topology_label="False", has_topology_key="False", pool_affinity_topology_key="True") + create_request[CREATE_REQUEST_KEY] = request + @given( parsers.parse( @@ -265,7 +369,7 @@ def a_request_for_a_replica_replica_volume_with_poolaffinitytopologylabel_as_poo ): """a request for a replica volume with poolAffinityTopologyLabel as and pool topology inclusion as .""" if pool_affinity_topology_label == "True": - request = create_volume_body(replica, volume_pool_topology_inclusion_label) + request = create_volume_body(replica, volume_pool_topology_inclusion_label, pool_affinity_topology_label="True", has_topology_key="False", pool_affinity_topology_key="False") create_request[CREATE_REQUEST_KEY] = request @@ -279,7 +383,7 @@ def a_request_for_a_replica_replica_volume_with_poolhastopologykey_as_has_topolo ): """a request for a replica volume with poolHasTopologyKey as and pool topology inclusion as .""" if has_topology_key == "True": - request = create_volume_body(replica, volume_pool_topology_inclusion_label) + request = create_volume_body(replica, volume_pool_topology_inclusion_label, pool_affinity_topology_label="False", has_topology_key="True", pool_affinity_topology_key="False") create_request[CREATE_REQUEST_KEY] = request @@ -296,6 +400,9 @@ def the_desired_number_of_replica_of_volume_ie_replica_here_is_expression_number create_request[CREATE_REQUEST_KEY]["topology"]["pool_topology"]["labelled"][ "inclusion" ], + create_request[CREATE_REQUEST_KEY]["topology"]["pool_topology"]["labelled"][ + "affinitykey" + ], ) if expression == "<=": assert int(replica) <= no_of_eligible_pools @@ -320,6 +427,7 @@ def the_replica_replica_volume_creation_should_result_and_provisioned_provisione replica, create_request[CREATE_REQUEST_KEY]["topology"] ) assert str(volume.spec) == str(expected_spec) + pools_names_which_has_given_labels = get_pool_names_with_given_labels( pool_label ) @@ -366,20 +474,32 @@ def get_pool_names_with_given_labels(pool_label): # Return the create volume request body based on the input parameters. -def create_volume_body(replica, volume_pool_topology_inclusion_label): +def create_volume_body(replica, volume_pool_topology_inclusion_label, pool_affinity_topology_label, has_topology_key, pool_affinity_topology_key ): """Create a volume body.""" - key, _, value = volume_pool_topology_inclusion_label.partition("=") - topology = Topology( - pool_topology=PoolTopology( - labelled=LabelledTopology( - exclusion={}, - inclusion={ - key.strip(): value.strip(), - DISKPOOL_LABEL_KEY: DISKPOOL_LABEL_VAL, - }, + if pool_affinity_topology_label == "True" or has_topology_key == "True" : + key, _, value = volume_pool_topology_inclusion_label.partition("=") + topology = Topology( + pool_topology=PoolTopology( + labelled=LabelledTopology( + exclusion={}, + inclusion={ + key.strip(): value.strip(), + DISKPOOL_LABEL_KEY: DISKPOOL_LABEL_VAL, + }, + affinitykey=[], + ) + ) + ) + elif pool_affinity_topology_key == "True": + topology = Topology( + pool_topology=PoolTopology( + labelled=LabelledTopology( + exclusion={}, + inclusion={}, + affinitykey=[volume_pool_topology_inclusion_label], + ) ) ) - ) return CreateVolumeBody( VolumePolicy(False), int(replica), @@ -405,15 +525,22 @@ def expected_volume_spec(replica, toplogy): # Return the number of pools that qualify based on the volume topology inclusion labels. -def no_of_suitable_pools(volume_pool_topology_inclusion_labels): +def no_of_suitable_pools(volume_pool_topology_inclusion_labels, volume_pool_topology_affinity): """Return the number of pools that qualify based on the volume topology inclusion labels.""" pool_with_labels = get_pool_names_with_its_corresponding_labels() - qualified_pools = list() + qualified_pools = set() # Using HashSet to avoid duplicate entries. for pool_id, pool_labels in pool_with_labels.items(): - if does_pool_qualify_inclusion_labels( - volume_pool_topology_inclusion_labels, pool_labels - ): - qualified_pools.append(pool_id) + if len(volume_pool_topology_inclusion_labels) !=0 : + if does_pool_qualify_inclusion_labels( + volume_pool_topology_inclusion_labels, pool_labels + ): + qualified_pools.add(pool_id) + + if len(volume_pool_topology_affinity) != 0 : + if does_pool_qualify_affinity_keys( + volume_pool_topology_affinity, pool_labels + ): + qualified_pools.add(pool_id) return len(qualified_pools) @@ -443,3 +570,16 @@ def does_pool_qualify_inclusion_labels( else: inc_match = False return inc_match + +# Return whether the pool qualifies based on the volume topology affinity keys. +def does_pool_qualify_affinity_keys( + volume_pool_topology_affinit_key, pool_labels +): + inc_key_match = True + for key in volume_pool_topology_affinit_key: + if key in pool_labels: + inc_key_match = True + break + else: + inc_key_match = False + return inc_key_match \ No newline at end of file