Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 116 additions & 50 deletions coriolis/tests/integration/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ def setUpClass(cls):
cls._imp_conn_info = cls._harness.imp_conn_info
cls._imp_env_options = cls._harness.imp_env_options
cls._storage_mappings = cls._harness.imp_storage_mappings
cls._pool_env = cls._harness.imp_minion_pool_environment
cls._imp_pool_env = cls._harness.imp_minion_pool_environment
cls._exp_pool_env = cls._harness.exp_minion_pool_environment

cls._client = cls.get_client()

Expand Down Expand Up @@ -168,13 +169,19 @@ def _create_pool(
name="test-pool",
skip_allocation=True,
wait_for_allocation=False,
platform=constants.PROVIDER_PLATFORM_DESTINATION,
):
env_options = (
cls._imp_pool_env
if platform == constants.PROVIDER_PLATFORM_DESTINATION
else cls._exp_pool_env
)
pool = cls._client.minion_pools.create(
name=name,
endpoint=endpoint_id,
platform=constants.PROVIDER_PLATFORM_DESTINATION,
platform=platform,
os_type=constants.OS_TYPE_LINUX,
environment_options=cls._pool_env,
environment_options=env_options,
minimum_minions=1,
maximum_minions=1,
minion_max_idle_time=3600,
Expand Down Expand Up @@ -241,6 +248,39 @@ def _get_db_context():
is_admin=True,
)

def assertPoolAllocated(self, pool_id):
"""Assert the pool is healthy and still in ALLOCATED status."""
ctxt = self._get_db_context()
pool = db_api.get_minion_pool(ctxt, pool_id)
self.assertIsNotNone(pool, "Pool %s not found" % pool_id)
self.assertEqual(
constants.MINION_POOL_STATUS_ALLOCATED,
pool.status,
"Pool %s is not ALLOCATED (got %s)" % (pool_id, pool.status),
)

def assertMachinesAvailable(self, pool_id):
"""Assert all machines in the pool are AVAILABLE and have been used."""
ctxt = self._get_db_context()
pool = db_api.get_minion_pool(ctxt, pool_id, include_machines=True)
self.assertIsNotNone(pool, "Pool %s not found" % pool_id)
self.assertTrue(
pool.minion_machines,
"Pool %s has no minion machines" % pool_id,
)
for machine in pool.minion_machines:
self.assertEqual(
constants.MINION_MACHINE_STATUS_AVAILABLE,
machine.allocation_status,
"Machine %s in pool %s is not AVAILABLE (got %s)"
% (machine.id, pool_id, machine.allocation_status),
)
self.assertIsNotNone(
machine.last_used_at,
"Machine %s in pool %s has no last_used_at; "
"it may not have been used by the transfer" % (machine.id, pool_id),
)

@staticmethod
def _ignoreExc(func, ignored_exc=Exception):
"""Wrap the given function, ignoring exceptions."""
Expand All @@ -255,7 +295,8 @@ def f(*args, **kwargs):


class ReplicaIntegrationTestBase(CoriolisIntegrationTestBase):
_CREATE_MINION_POOLS = False
_CREATE_DST_MINION_POOL = False
_CREATE_SRC_MINION_POOL = False
_SRC_DEVICE_SIZE_MB = 16

# Extra source_environment entries merged into the default transfer's
Expand Down Expand Up @@ -283,15 +324,27 @@ def setUpClass(cls):
)

# Create minion pool if needed.
cls._pool_id = None
if cls._CREATE_MINION_POOLS:
cls._dst_pool_id = None
if cls._CREATE_DST_MINION_POOL:
pool = cls._create_pool(
cls._dst_endpoint.id,
"transfer-pool",
"dst-transfer-pool",
skip_allocation=False,
wait_for_allocation=True,
)
cls._pool_id = pool.id
cls._dst_pool_id = pool.id

# Create source minion pool if needed.
cls._src_pool_id = None
if cls._CREATE_SRC_MINION_POOL:
pool = cls._create_pool(
cls._src_endpoint.id,
"src-transfer-pool",
skip_allocation=False,
wait_for_allocation=True,
platform=constants.PROVIDER_PLATFORM_SOURCE,
)
cls._src_pool_id = pool.id

def setUp(self):
super().setUp()
Expand All @@ -316,7 +369,8 @@ def setUp(self):
self._src_endpoint.id,
self._dst_endpoint.id,
instances=[self._instance_name],
destination_minion_pool_id=self._pool_id,
destination_minion_pool_id=self._dst_pool_id,
origin_minion_pool_id=self._src_pool_id,
source_environment={
"instance_block_devices": {self._instance_name: [self._src_device]},
**self._EXTRA_SOURCE_ENVIRONMENT,
Expand Down Expand Up @@ -598,7 +652,28 @@ def _slow_call(*args, **kwargs):
self.addCleanup(patcher.stop)


class MinionPoolTestBase(CoriolisIntegrationTestBase):
class SourceMinionPoolTestBase(CoriolisIntegrationTestBase):
"""Base class for source minion pool integration tests.

Skips the entire test class when the export provider does not advertise
``PROVIDER_TYPE_SOURCE_MINION_POOL`` support.
"""

@classmethod
def setUpClass(cls):
h = harness._IntegrationHarness.get()
available = providers_factory.get_available_providers()
exp_types = available.get(h.exp_provider_platform, {}).get("types", [])
if constants.PROVIDER_TYPE_SOURCE_MINION_POOL not in exp_types:
raise unittest.SkipTest(
"Export provider '%s' does not support minion pools"
% h.exp_provider_platform
)

super().setUpClass()


class DestinationMinionPoolTestBase(CoriolisIntegrationTestBase):
"""Base class for minion pool integration tests.

Skips the entire test class when the import provider does not advertise
Expand All @@ -622,59 +697,50 @@ def setUpClass(cls):
super().setUpClass()


class MinionPoolReplicaTestBase(MinionPoolTestBase, ReplicaIntegrationTestBase):
"""Base class for replica integration tests using minion pools.
class MinionPoolReplicaTestBase(
DestinationMinionPoolTestBase, ReplicaIntegrationTestBase
):
"""Base class for replica integration tests using destination minion pools.

Extends the assertions to also verify that the minions in the pool have
been used, and that the minions and the pool returns to an available state.
"""

_CREATE_MINION_POOLS = True
_CREATE_DST_MINION_POOL = True

def _execute_and_wait(self, transfer_id, timeout=600):
super()._execute_and_wait(transfer_id, timeout=timeout)
self.assertPoolAllocated(self._pool_id)
self.assertMachinesAvailable(self._pool_id)
self.assertPoolAllocated(self._dst_pool_id)
self.assertMachinesAvailable(self._dst_pool_id)

def assertExecutionCompleted(self, execution_id, timeout=600):
super().assertExecutionCompleted(execution_id, timeout=timeout)
self.assertPoolAllocated(self._pool_id)
self.assertMachinesAvailable(self._pool_id)
self.assertPoolAllocated(self._dst_pool_id)
self.assertMachinesAvailable(self._dst_pool_id)

def assertDeploymentCompleted(self, deployment_id, timeout=600):
super().assertDeploymentCompleted(deployment_id, timeout=timeout)
self.assertPoolAllocated(self._pool_id)
self.assertMachinesAvailable(self._pool_id)
self.assertPoolAllocated(self._dst_pool_id)
self.assertMachinesAvailable(self._dst_pool_id)

def assertPoolAllocated(self, pool_id):
"""Assert the pool is healthy and still in ALLOCATED status."""
ctxt = self._get_db_context()
pool = db_api.get_minion_pool(ctxt, pool_id)
self.assertIsNotNone(pool, "Pool %s not found" % pool_id)
self.assertEqual(
constants.MINION_POOL_STATUS_ALLOCATED,
pool.status,
"Pool %s is not ALLOCATED (got %s)" % (pool_id, pool.status),
)

def assertMachinesAvailable(self, pool_id):
"""Assert all machines in the pool are AVAILABLE and have been used."""
ctxt = self._get_db_context()
pool = db_api.get_minion_pool(ctxt, pool_id, include_machines=True)
self.assertIsNotNone(pool, "Pool %s not found" % pool_id)
self.assertTrue(
pool.minion_machines,
"Pool %s has no minion machines" % pool_id,
)
for machine in pool.minion_machines:
self.assertEqual(
constants.MINION_MACHINE_STATUS_AVAILABLE,
machine.allocation_status,
"Machine %s in pool %s is not AVAILABLE (got %s)"
% (machine.id, pool_id, machine.allocation_status),
)
self.assertIsNotNone(
machine.last_used_at,
"Machine %s in pool %s has no last_used_at; "
"it may not have been used by the transfer" % (machine.id, pool_id),
)
class SourceMinionPoolReplicaTestBase(
SourceMinionPoolTestBase, ReplicaIntegrationTestBase
):
"""Base class for replica integration tests using source minion pools.

Extends the assertions to also verify that the minions in the pool have
been used, and that the minions and the pool returns to an available state.
"""

_CREATE_SRC_MINION_POOL = True

def _execute_and_wait(self, transfer_id, timeout=600):
super()._execute_and_wait(transfer_id, timeout=timeout)
self.assertPoolAllocated(self._src_pool_id)
self.assertMachinesAvailable(self._src_pool_id)

def assertExecutionCompleted(self, execution_id, timeout=600):
super().assertExecutionCompleted(execution_id, timeout=timeout)
self.assertPoolAllocated(self._src_pool_id)
self.assertMachinesAvailable(self._src_pool_id)
2 changes: 1 addition & 1 deletion coriolis/tests/integration/deployments/test_osmorphing.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ def test_os_morphing_global_script_first_boot(self):


class OsMorphingMinionPoolDeploymentTest(
integration_base.MinionPoolTestBase, OsMorphingDeploymentTestBase
integration_base.DestinationMinionPoolTestBase, OsMorphingDeploymentTestBase
):
"""OS morphing deployment using a minion pool for the OS morphing phase."""

Expand Down
1 change: 1 addition & 0 deletions coriolis/tests/integration/harness.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ def __init__(self):
"pkey_path": self.ssh_key_path,
"role": "source",
}
self.exp_minion_pool_environment = {}

# Init importer.
imp_provider_cls = providers_config["destination"]["provider_cls"]
Expand Down
10 changes: 10 additions & 0 deletions coriolis/tests/integration/test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- get_storage (list and default)
- get_source_environment_options
- get_target_environment_options
- get_source_minion_pool_options
- get_destination_minion_pool_options
- get_inventory_csv
- endpoint_instances.list and endpoint_instances.get
Expand Down Expand Up @@ -93,6 +94,15 @@ def test_list_destination_options(self):
self.assertIsInstance(options, list)
self.assertTrue(len(options) > 0, "Expected at least one destination option")

def test_list_source_minion_pool_options(self):
options = self._client.endpoint_source_minion_pool_options.list(
self._src_endpoint.id
)
self.assertIsInstance(options, list)
self.assertTrue(
len(options) > 0, "Expected at least one source minion pool option"
)

def test_list_destination_minion_pool_options(self):
if not isinstance(
self._imp_provider, provider_base.BaseDestinationMinionPoolProvider
Expand Down
4 changes: 2 additions & 2 deletions coriolis/tests/integration/test_failure_recovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,13 @@ def test_transfer_minion_allocation_failure_cleans_up(self):
mock_create.assert_called()

# The pool itself stays usable.
self.assertPoolAllocated(self._pool_id)
self.assertPoolAllocated(self._dst_pool_id)

# Its only machine failed both the healthcheck and the recreation
# attempt. ending up as UNINITIALIZED. It then gets deleted, rather
# than left dangling in a broken intermediate status.
ctxt = self._get_db_context()
pool = db_api.get_minion_pool(ctxt, self._pool_id, include_machines=True)
pool = db_api.get_minion_pool(ctxt, self._dst_pool_id, include_machines=True)
self.assertEqual(
[],
pool.minion_machines,
Expand Down
49 changes: 40 additions & 9 deletions coriolis/tests/integration/test_minion_pools.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,8 @@
CONF = cfg.CONF


class MinionPoolLifecycleTest(base.MinionPoolTestBase):
def setUp(self):
super().setUp()

self._endpoint = self._create_endpoint(
name="pool-dst",
endpoint_type=self._imp_platform,
connection_info=self._imp_conn_info,
)
class MinionPoolLifecycleTestMixin:
_MINION_PLATFORM = None

def _wait_for_machine_status(self, pool_id, status, timeout=120):
"""Poll the DB until the pool's single machine reaches *status*."""
Expand All @@ -54,6 +47,7 @@ def test_minion_pool_crud(self):
pool = self._create_pool(self._endpoint.id)

self.assertEqual("test-pool", pool.name)
self.assertEqual(self._MINION_PLATFORM, pool.platform)
self.assertEqual(constants.MINION_POOL_STATUS_DEALLOCATED, pool.status)

# List
Expand Down Expand Up @@ -115,6 +109,22 @@ def test_allocate_deallocate(self):
"Pool deallocation ended in unexpected status '%s'" % final.status,
)


class MinionPoolLifecycleTests(
MinionPoolLifecycleTestMixin, base.DestinationMinionPoolTestBase
):
_MINION_PLATFORM = constants.PROVIDER_PLATFORM_DESTINATION

def setUp(self):
super().setUp()

self._endpoint = self._create_endpoint(
name="pool-dst",
endpoint_type=self._imp_platform,
connection_info=self._imp_conn_info,
)
self._pool_env = self._imp_pool_env

def test_cron_triggered_refresh(self):
"""Cron-scheduled refresh.

Expand Down Expand Up @@ -163,3 +173,24 @@ def test_cron_triggered_refresh(self):
"Minion pool machine '%s' was not refreshed by the automatic "
"cron job in time" % pool.id,
)


class SourceMinionPoolLifecycleTests(
MinionPoolLifecycleTestMixin, base.SourceMinionPoolTestBase
):
_MINION_PLATFORM = constants.PROVIDER_PLATFORM_SOURCE

def setUp(self):
super().setUp()

self._endpoint = self._create_endpoint(
name="pool-src",
endpoint_type=self._exp_platform,
connection_info=self._exp_conn_info,
)
self._pool_env = self._exp_pool_env

def _create_pool(self, endpoint_id, **kwargs):
return super()._create_pool(
endpoint_id, platform=constants.PROVIDER_PLATFORM_SOURCE, **kwargs
)
Loading
Loading