distcloud/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_peer_group_association.py

360 lines
15 KiB
Python

#
# Copyright (c) 2023 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import mock
from six.moves import http_client
import uuid
from dcmanager.db.sqlalchemy import api as db_api
from dcmanager.rpc import client as rpc_client
from dcmanager.api.controllers.v1 import peer_group_association
from dcmanager.common import phased_subcloud_deploy as psd_common
from dcmanager.tests.unit.api import test_root_controller as testroot
from dcmanager.tests.unit.api.v1.controllers.mixins import APIMixin
from dcmanager.tests.unit.api.v1.controllers.mixins import GetMixin
from dcmanager.tests.unit.api.v1.controllers.mixins import UpdateMixin
from dcmanager.tests import utils
# SAMPLE SYSTEM PEER DATA
SAMPLE_SYSTEM_PEER_UUID = str(uuid.uuid4())
SAMPLE_SYSTEM_PEER_NAME = 'SystemPeer1'
SAMPLE_MANAGER_ENDPOINT = 'http://127.0.0.1:5000'
SAMPLE_MANAGER_USERNAME = 'admin'
SAMPLE_MANAGER_PASSWORD = 'password'
SAMPLE_PEER_CONTROLLER_GATEWAY_IP = '128.128.128.1'
SAMPLE_ADMINISTRATIVE_STATE = 'enabled'
SAMPLE_HEARTBEAT_INTERVAL = 10
SAMPLE_HEARTBEAT_FAILURE_THRESHOLD = 3
SAMPLE_HEARTBEAT_FAILURES_POLICY = 'alarm'
SAMPLE_HEARTBEAT_MAINTENANCE_TIMEOUT = 600
SAMPLE_HEARTBEAT_STATUS_ALIVE = 'alive'
# SAMPLE SUBCLOUD PEER GROUP DATA
SAMPLE_SUBCLOUD_PEER_GROUP_NAME = 'GroupX'
SAMPLE_SUBCLOUD_PEER_GROUP_SYSTEM_LEADER_ID = str(uuid.uuid4())
SAMPLE_SUBCLOUD_PEER_GROUP_SYSTEM_LEADER_NAME = 'dc-local'
SAMPLE_SUBCLOUD_PEER_GROUP_MAX_SUBCLOUDS_REHOMING = 50
SAMPLE_SUBCLOUD_PEER_GROUP_PRIORITY = 0
SAMPLE_SUBCLOUD_PEER_GROUP_STATE = 'enabled'
# SAMPLE PEER GROUP ASSOCIATION DATA
SAMPLE_SUBCLOUD_PEER_GROUP_ID = 1
SAMPLE_SYSTEM_PEER_ID = 1
SAMPLE_PEER_GROUP_PRIORITY = 1
SAMPLE_PEER_GROUP_PRIORITY_UPDATED = 99
SAMPLE_SYNC_STATUS = 'synced'
SAMPLE_SYNC_MESSAGE = 'None'
class FakeSystem(object):
def __init__(self, uuid):
self.uuid = uuid
class FakeKeystoneClient(object):
def __init__(self):
self.keystone_client = mock.MagicMock()
self.session = mock.MagicMock()
self.endpoint_cache = mock.MagicMock()
class FakeSysinvClient(object):
def __init__(self):
self.system = FakeSystem(SAMPLE_SUBCLOUD_PEER_GROUP_SYSTEM_LEADER_ID)
def get_system(self):
return self.system
class PeerGroupAssociationAPIMixin(APIMixin):
API_PREFIX = '/v1.0/peer-group-associations'
RESULT_KEY = 'peer_group_associations'
EXPECTED_FIELDS = ['id',
'peer-group-id',
'system-peer-id',
'peer-group-priority',
'created-at',
'updated-at']
def setUp(self):
super(PeerGroupAssociationAPIMixin, self).setUp()
self.fake_rpc_client.some_method = mock.MagicMock()
def _get_test_system_peer_dict(self, **kw):
# id should not be part of the structure
system_peer = {
'peer_uuid': kw.get('peer_uuid', SAMPLE_SYSTEM_PEER_UUID),
'peer_name': kw.get('peer_name', SAMPLE_SYSTEM_PEER_NAME),
'endpoint': kw.get('manager_endpoint', SAMPLE_MANAGER_ENDPOINT),
'username': kw.get('manager_username', SAMPLE_MANAGER_USERNAME),
'password': kw.get('manager_password', SAMPLE_MANAGER_PASSWORD),
'gateway_ip': kw.get(
'peer_controller_gateway_ip', SAMPLE_PEER_CONTROLLER_GATEWAY_IP),
'administrative_state': kw.get('administrative_state',
SAMPLE_ADMINISTRATIVE_STATE),
'heartbeat_interval': kw.get('heartbeat_interval',
SAMPLE_HEARTBEAT_INTERVAL),
'heartbeat_failure_threshold': kw.get(
'heartbeat_failure_threshold', SAMPLE_HEARTBEAT_FAILURE_THRESHOLD),
'heartbeat_failure_policy': kw.get(
'heartbeat_failure_policy', SAMPLE_HEARTBEAT_FAILURES_POLICY),
'heartbeat_maintenance_timeout': kw.get(
'heartbeat_maintenance_timeout',
SAMPLE_HEARTBEAT_MAINTENANCE_TIMEOUT)
}
return system_peer
def _get_test_subcloud_peer_group_dict(self, **kw):
# id should not be part of the structure
group = {
'peer_group_name': kw.get('peer_group_name',
SAMPLE_SUBCLOUD_PEER_GROUP_NAME),
'system_leader_id': kw.get(
'system_leader_id',
SAMPLE_SUBCLOUD_PEER_GROUP_SYSTEM_LEADER_ID),
'system_leader_name': kw.get(
'system_leader_name',
SAMPLE_SUBCLOUD_PEER_GROUP_SYSTEM_LEADER_NAME),
'group_priority': kw.get(
'group_priority',
SAMPLE_SUBCLOUD_PEER_GROUP_PRIORITY),
'group_state': kw.get(
'group_state',
SAMPLE_SUBCLOUD_PEER_GROUP_STATE),
'max_subcloud_rehoming': kw.get(
'max_subcloud_rehoming',
SAMPLE_SUBCLOUD_PEER_GROUP_MAX_SUBCLOUDS_REHOMING),
'migration_status': None
}
return group
def _get_test_peer_group_association_dict(self, **kw):
# id should not be part of the structure
association = {
'peer_group_id': kw.get('peer_group_id',
SAMPLE_SUBCLOUD_PEER_GROUP_ID),
'system_peer_id': kw.get('system_peer_id', SAMPLE_SYSTEM_PEER_ID),
'peer_group_priority': kw.get('peer_group_priority',
SAMPLE_PEER_GROUP_PRIORITY),
'sync_status': kw.get('sync_status', SAMPLE_SYNC_STATUS),
'sync_message': kw.get('sync_message', SAMPLE_SYNC_MESSAGE)
}
return association
def _post_get_test_peer_group_association(self, **kw):
post_body = self._get_test_peer_group_association_dict(**kw)
return post_body
# The following methods are required for subclasses of APIMixin
def get_api_prefix(self):
return self.API_PREFIX
def get_result_key(self):
return self.RESULT_KEY
def get_expected_api_fields(self):
return self.EXPECTED_FIELDS
def get_omitted_api_fields(self):
return []
def _create_db_related_objects(self, context):
system_peer_fields = self._get_test_system_peer_dict()
peer = db_api.system_peer_create(context, **system_peer_fields)
peer_group_fields = self._get_test_subcloud_peer_group_dict()
peer_group = db_api.subcloud_peer_group_create(context,
**peer_group_fields)
return peer.id, peer_group.id
def _create_db_object(self, context, **kw):
peer_id, peer_group_id = self._create_db_related_objects(context)
kw['peer_group_id'] = peer_group_id if kw.get('peer_group_id') is None \
else kw.get('peer_group_id')
kw['system_peer_id'] = peer_id if kw.get('system_peer_id') is None \
else kw.get('system_peer_id')
creation_fields = self._get_test_peer_group_association_dict(**kw)
return db_api.peer_group_association_create(context, **creation_fields)
def get_post_object(self):
return self._post_get_test_peer_group_association()
def get_update_object(self):
update_object = {
'peer_group_priority': SAMPLE_PEER_GROUP_PRIORITY_UPDATED
}
return update_object
# Combine Peer Group Association API with mixins to test post, get, update and delete
class TestPeerGroupAssociationPost(testroot.DCManagerApiTest,
PeerGroupAssociationAPIMixin):
def setUp(self):
super(TestPeerGroupAssociationPost, self).setUp()
p = mock.patch.object(rpc_client, 'ManagerClient')
self.mock_rpc_client = p.start()
self.addCleanup(p.stop)
context = utils.dummy_context()
self.context = context
peer_id, _ = self._create_db_related_objects(context)
db_api.system_peer_update(context, peer_id=peer_id,
heartbeat_status=SAMPLE_HEARTBEAT_STATUS_ALIVE)
def verify_post_failure(self, response):
# Failures will return text rather than JSON
self.assertEqual(response.content_type, 'text/plain')
self.assertEqual(response.status_code, http_client.BAD_REQUEST)
def test_create_success(self):
self.mock_rpc_client().sync_subcloud_peer_group.return_value = True
ndict = self.get_post_object()
response = self.app.post_json(self.get_api_prefix(),
ndict,
headers=self.get_api_headers())
self.assertEqual(response.content_type, 'application/json')
def test_create_with_string_id_fails(self):
# A string system peer id is not permitted.
ndict = self.get_post_object()
ndict['system_peer_id'] = 'test-system-peer-id'
response = self.app.post_json(self.get_api_prefix(),
ndict,
headers=self.get_api_headers(),
expect_errors=True)
self.verify_post_failure(response)
def test_create_with_blank_id_fails(self):
# An empty system_peer_id is not permitted
ndict = self.get_post_object()
ndict['system_peer_id'] = ''
response = self.app.post_json(self.get_api_prefix(),
ndict,
headers=self.get_api_headers(),
expect_errors=True)
self.verify_post_failure(response)
def test_create_with_wrong_peer_group_priority_fails(self):
# A string peer group priority is not permitted.
ndict = self.get_post_object()
ndict['peer_group_id'] = 'peer-group-id'
response = self.app.post_json(self.get_api_prefix(),
ndict,
headers=self.get_api_headers(),
expect_errors=True)
self.verify_post_failure(response)
def test_create_with_bad_peer_group_priority(self):
# peer_group_priority must be an integer between 1 and 65536
ndict = self.get_post_object()
# All the entries in bad_values should be considered invalid
bad_values = [0, 65537, -2, 'abc']
for bad_value in bad_values:
ndict['peer_group_priority'] = bad_value
response = self.app.post_json(self.get_api_prefix(),
ndict,
headers=self.get_api_headers(),
expect_errors=True)
self.verify_post_failure(response)
class TestPeerGroupAssociationGet(testroot.DCManagerApiTest,
PeerGroupAssociationAPIMixin,
GetMixin):
def setUp(self):
super(TestPeerGroupAssociationGet, self).setUp()
class TestPeerGroupAssociationUpdate(testroot.DCManagerApiTest,
PeerGroupAssociationAPIMixin,
UpdateMixin):
def setUp(self):
super(TestPeerGroupAssociationUpdate, self).setUp()
def validate_updated_fields(self, sub_dict, full_obj):
for key, value in sub_dict.items():
key = key.replace('_', '-')
self.assertEqual(value, full_obj.get(key))
@mock.patch.object(rpc_client, 'ManagerClient')
def test_update_success(self, mock_client):
mock_client().update_subcloud_peer_group.return_value = {
'peer-group-priority': SAMPLE_PEER_GROUP_PRIORITY_UPDATED
}
context = utils.dummy_context()
single_obj = self._create_db_object(context)
update_data = self.get_update_object()
response = self.app.patch_json(self.get_single_url(single_obj.id),
headers=self.get_api_headers(),
params=update_data)
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
self.validate_updated_fields(update_data, response.json)
@mock.patch.object(psd_common, 'OpenStackDriver')
@mock.patch.object(peer_group_association, 'SysinvClient')
@mock.patch.object(rpc_client, 'ManagerClient')
def test_sync_association(self, mock_client, mock_sysinv_client, mock_keystone_client):
mock_client().sync_subcloud_peer_group.return_value = True
mock_keystone_client().keystone_client = FakeKeystoneClient()
mock_sysinv_client.return_value = FakeSysinvClient()
context = utils.dummy_context()
single_obj = self._create_db_object(context)
response = self.app.patch_json(
self.get_single_url(single_obj.id) + '/sync',
headers=self.get_api_headers())
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
mock_client().sync_subcloud_peer_group.assert_called_once()
class TestPeerGroupAssociationDelete(testroot.DCManagerApiTest,
PeerGroupAssociationAPIMixin):
def setUp(self):
super(TestPeerGroupAssociationDelete, self).setUp()
p = mock.patch.object(rpc_client, 'ManagerClient')
self.mock_rpc_client = p.start()
self.addCleanup(p.stop)
self.mock_rpc_client().delete_peer_group_association.return_value = True
def test_delete_success(self):
context = utils.dummy_context()
single_obj = self._create_db_object(context)
response = self.app.delete(self.get_single_url(single_obj.id),
headers=self.get_api_headers())
self.mock_rpc_client().delete_peer_group_association. \
assert_called_once()
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
def test_double_delete(self):
context = utils.dummy_context()
single_obj = self._create_db_object(context)
response = self.app.delete(self.get_single_url(single_obj.id),
headers=self.get_api_headers())
self.mock_rpc_client().delete_peer_group_association. \
assert_called_once()
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.status_code, http_client.OK)
db_api.peer_group_association_destroy(context, single_obj.id)
# delete the same object a second time. this should fail (NOT_FOUND)
response = self.app.delete(self.get_single_url(single_obj.id),
headers=self.get_api_headers(),
expect_errors=True)
self.assertEqual(response.content_type, 'text/plain')
self.assertEqual(response.status_code, http_client.NOT_FOUND)