Skip to content

Commit

Permalink
Add unit tests for Coriolis Scheduler RPC Server
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergiu Miclea committed Nov 7, 2023
1 parent d03d049 commit b13066f
Show file tree
Hide file tree
Showing 5 changed files with 355 additions and 0 deletions.
Empty file.
Empty file.
224 changes: 224 additions & 0 deletions coriolis/tests/scheduler/rpc/data/get_workers_for_specs_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
## ENABLED TESTS ##

# no filters, but services available
- config:
services_db:
- id: 1
topic: coriolis_worker
- id: 2
topic: coriolis_scheduler
- id: 3
topic: coriolis_worker
expected_result: [1, 3]
expected_exception: ~

# different topic and enabled combinations
- config:
enabled: true
services_db:
- id: 1
topic: coriolis_worker
enabled: true
- id: 2
topic: coriolis_worker
enabled: false
- id: 3
topic: coriolis_worker
enabled: true
- id: 4
topic: coriolis_scheduler
enabled: true
expected_result: [1, 3]
expected_exception: ~

# REGIONS TESTS ##

- config:
region_sets: [[region_1, region_2], [region_3]]
regions_db:
- id: region_1
enabled: true
- id: region_2
enabled: false
- id: region_3
enabled: true
services_db:
- id: 1
topic: coriolis_worker
mapped_regions:
- id: region_1
- id: region_3
- id: 2
topic: coriolis_worker
mapped_regions:
- id: region_2
- id: region_3
# region_3 not mapped
- id: 3
topic: coriolis_worker
mapped_regions:
- id: region_2
- id: 4
topic: coriolis_worker
mapped_regions:
- id: region_1
- id: region_2
- id: region_3
- id: 5
topic: coriolis_worker
mapped_regions:
- id: invalid_region
expected_result: [1, 2, 4]
expected_exception: ~

# region_3 is disabled in DB
- config:
region_sets: [[region_1, region_2], [region_3]]
regions_db: ~
services_db:
- id: 1
topic: coriolis_worker
mapped_regions:
- id: region_1
- id: region_3
expected_result: ~
expected_exception: NoSuitableRegionError

## PROVIDERS TESTS ##

- config:
provider_requirements:
provider_1: [1, 2, 16]
provider_2: [1, 2]
provider_3: [1, 32]
services_db:
- id: 1
topic: coriolis_worker
providers:
provider_1:
types: [1, 2, 16]
provider_2:
types: [1, 2, 16]
provider_3:
types: [1, 2, 16, 32]
# 2 is missing provider_3 with 32
- id: 2
topic: coriolis_worker
providers:
provider_1:
types: [1, 2, 16]
provider_2:
types: [1, 2, 16]
provider_3:
types: [1, 2, 16]
- id: 3
topic: coriolis_worker
providers:
provider_1:
types: [1, 2, 16, 32]
provider_2:
types: [1, 2, 16, 32]
provider_3:
types: [1, 2, 16, 32, 64]
expected_result: [1, 3]
expected_exception: ~

## ALL TOGETHER: ENABLED, REGIONS AND PROVIDER ##

- config:
enabled: true
region_sets: [[region_1, region_2], [region_3]]
provider_requirements:
provider_1: [1, 2, 16]
provider_2: [1, 2]
provider_3: [1, 32]
regions_db:
- id: region_1
enabled: true
- id: region_2
enabled: false
- id: region_3
enabled: true
services_db:
- id: 1
topic: coriolis_worker
enabled: true
mapped_regions:
- id: region_1
- id: region_3
providers:
provider_1:
types: [1, 2, 16]
provider_2:
types: [1, 2, 16]
provider_3:
types: [1, 2, 16, 32]
# 2 is missing provider_3 with 32
- id: 2
topic: coriolis_worker
enabled: true
mapped_regions:
- id: region_2
- id: region_3
providers:
provider_1:
types: [1, 2, 16]
provider_2:
types: [1, 2, 16]
provider_3:
types: [1, 2, 16]
- id: 3
topic: coriolis_worker
enabled: true
mapped_regions:
- id: region_1
- id: region_2
- id: region_3
providers:
provider_1:
types: [1, 2, 16, 32]
provider_2:
types: [1, 2, 16, 32]
provider_3:
types: [1, 2, 16, 32, 64]
- id: 4
topic: coriolis_worker
enabled: true
# missing region_1 or region_2
mapped_regions:
- id: region_3
providers:
provider_1:
types: [1, 2, 16, 32]
provider_2:
types: [1, 2, 16, 32]
provider_3:
types: [1, 2, 16, 32, 64]
- id: 5
topic: coriolis_worker
enabled: true
mapped_regions:
- id: region_1
- id: region_3
providers:
provider_1:
types: [1, 2, 16, 32]
provider_2:
types: [1, 2, 16, 32]
provider_3:
types: [1, 2, 16, 32, 64]
- id: 6
topic: coriolis_worker
# is not enabled
mapped_regions:
- id: region_1
- id: region_3
providers:
provider_1:
types: [1, 2, 16, 32]
provider_2:
types: [1, 2, 16, 32]
provider_3:
types: [1, 2, 16, 32, 64]
expected_result: [1, 3, 5]
expected_exception: ~
98 changes: 98 additions & 0 deletions coriolis/tests/scheduler/rpc/test_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Copyright 2023 Cloudbase Solutions Srl
# All Rights Reserved.

from unittest import mock

import ddt


from coriolis import exception
from coriolis.tests import test_base, testutils
from coriolis.scheduler.rpc import server


@ddt.ddt
class SchedulerServerEndpointTestCase(test_base.CoriolisBaseTestCase):
"""Test suite for the Coriolis Scheduler Worker RPC server."""

def setUp(self):
super(SchedulerServerEndpointTestCase, self).setUp()
self.server = server.SchedulerServerEndpoint()

@mock.patch.object(
server.SchedulerServerEndpoint, '_get_weighted_filtered_services'
)
@mock.patch.object(
server.SchedulerServerEndpoint, '_filter_regions'
)
@mock.patch.object(
server.SchedulerServerEndpoint, '_get_all_worker_services'
)
@ddt.file_data("data/get_workers_for_specs_config.yaml")
@ddt.unpack
def test_get_workers_for_specs(
self,
mock_get_all_worker_services,
mock_filter_regions,
mock_get_weighted_filtered_services,
config,
expected_result,
expected_exception,
):

enabled = config.get("enabled", None)
region_sets = config.get("region_sets", None)
provider_requirements = config.get("provider_requirements", None)

# Convert the config dict to an object, skipping the providers
# providers is the only field used as dict in the code
config_obj = testutils.DictToObject(config, skip_attrs=["providers"])
mock_get_all_worker_services.return_value = (
config_obj.services_db or []
)
mock_filter_regions.return_value = config_obj.regions_db or []
mock_get_weighted_filtered_services.return_value = \
[] if expected_result is None else [
(mock.Mock(id=expected_id), 100)
for expected_id in expected_result
]

kwargs = {
"enabled": enabled,
"region_sets": region_sets,
"provider_requirements": provider_requirements,
}
if expected_exception:
exception_type = getattr(exception, expected_exception)
self.assertRaises(
exception_type,
self.server.get_workers_for_specs,
mock.sentinel.context,
**kwargs
)
return

result = self.server.get_workers_for_specs(
mock.sentinel.context,
**kwargs
)

mock_get_all_worker_services.assert_called_once_with(
mock.sentinel.context)

if region_sets:
for region_set in region_sets:
mock_filter_regions.assert_any_call(
mock.sentinel.context,
region_set,
enabled=True,
check_all_exist=True
)

mock_get_weighted_filtered_services.assert_called_once_with(
mock_get_all_worker_services.return_value, mock.ANY
)

id_array = [worker.id for worker in result]

self.assertEqual(id_array, expected_result)
33 changes: 33 additions & 0 deletions coriolis/tests/testutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,36 @@ def _get_wrapped_function(function):
return function

return _get_wrapped_function(function)


class DictToObject:
"""Converts a dictionary to an object with attributes.
This is useful for mocking objects that are used as configuration
objects.
"""

def __init__(self, dictionary, skip_attrs=None):
if skip_attrs is None:
skip_attrs = []

for key, value in dictionary.items():
if key in skip_attrs:
setattr(self, key, value)
elif isinstance(value, dict):
setattr(self, key, DictToObject(value, skip_attrs=skip_attrs))
elif isinstance(value, list):
setattr(
self, key,
[DictToObject(item, skip_attrs=skip_attrs) if isinstance(
item, dict) else item for item in value])
else:
setattr(self, key, value)

def __getattr__(self, item):
return None

def __repr__(self):
attrs = [f"{k}={v!r}" for k, v in self.__dict__.items()]
attrs_str = ', '.join(attrs)
return f"{self.__class__.__name__}({attrs_str})"

0 comments on commit b13066f

Please sign in to comment.