# # Copyright (c) 2019 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # """ Tests for the API / service_parameter / methods. """ from six.moves import http_client from oslo_utils import uuidutils from sysinv.common import constants from sysinv.tests.api import base from sysinv.tests.db import base as dbbase from sysinv.tests.db import utils as dbutils class ApiServiceParameterTestCaseMixin(object): # API_HEADERS are a generic header passed to most API calls API_HEADERS = {'User-Agent': 'sysinv-test', 'Content-Type': 'application/json', 'Accept': 'application/json'} # API_PREFIX is the prefix for the URL API_PREFIX = '/service_parameter' # RESULT_KEY is the python table key for the list of results RESULT_KEY = 'parameters' # expected_api_fields are attributes that should be populated by # an API query expected_api_fields = ['uuid', 'service', 'section', 'name', 'value', 'resource', 'personality' ] required_post_fields = [ 'service', 'section', 'parameters' 'resource', 'personality' ] # hidden_api_fields are attributes that should not be populated by # an API query hidden_api_fields = [] service_parameter_data = [ { 'service': constants.SERVICE_TYPE_HTTP, 'section': constants.SERVICE_PARAM_SECTION_HTTP_CONFIG, 'name': constants.SERVICE_PARAM_HTTP_PORT_HTTP, 'value': str(constants.SERVICE_PARAM_HTTP_PORT_HTTP_DEFAULT) }, { 'service': constants.SERVICE_TYPE_HTTP, 'section': constants.SERVICE_PARAM_SECTION_HTTP_CONFIG, 'name': constants.SERVICE_PARAM_HTTP_PORT_HTTPS, 'value': str(constants.SERVICE_PARAM_HTTP_PORT_HTTPS_DEFAULT) }, { 'service': constants.SERVICE_TYPE_KUBERNETES, 'section': constants.SERVICE_PARAM_SECTION_KUBERNETES_CERTIFICATES, 'name': constants.SERVICE_PARAM_NAME_KUBERNETES_API_SAN_LIST, 'value': 'localurl' }, { 'service': constants.SERVICE_TYPE_KUBERNETES, 'section': constants.SERVICE_PARAM_SECTION_KUBERNETES_APISERVER, 'name': constants.SERVICE_PARAM_NAME_OIDC_USERNAME_CLAIM, 'value': 'wad' }, { 'service': constants.SERVICE_TYPE_KUBERNETES, 'section': constants.SERVICE_PARAM_SECTION_KUBERNETES_APISERVER, 'name': constants.SERVICE_PARAM_NAME_OIDC_ISSUER_URL, 'value': 'https://10.10.10.3:30556/dex' }, { 'service': constants.SERVICE_TYPE_KUBERNETES, 'section': constants.SERVICE_PARAM_SECTION_KUBERNETES_APISERVER, 'name': constants.SERVICE_PARAM_NAME_OIDC_CLIENT_ID, 'value': 'wad' }, { 'service': constants.SERVICE_TYPE_KUBERNETES, 'section': constants.SERVICE_PARAM_SECTION_KUBERNETES_APISERVER, 'name': constants.SERVICE_PARAM_NAME_OIDC_GROUPS_CLAIM, 'value': 'wad' } ] service_parameter_wildcard = { 'service': constants.SERVICE_TYPE_PTP, 'section': constants.SERVICE_PARAM_SECTION_PTP_GLOBAL, 'name': 'network_transport', 'value': 'L2' } def setUp(self): super(ApiServiceParameterTestCaseMixin, self).setUp() def get_single_url(self, uuid): return '%s/%s' % (self.API_PREFIX, uuid) # These methods have generic names and are overridden here # Future activity: Redo the subclasses to use mixins def assert_fields(self, api_object): # check the uuid is a uuid assert(uuidutils.is_uuid_like(api_object['uuid'])) # Verify that expected attributes are returned for field in self.expected_api_fields: self.assertIn(field, api_object) # Verify that hidden attributes are not returned for field in self.hidden_api_fields: self.assertNotIn(field, api_object) def _create_db_object(self, parameter_data=None): if not parameter_data: parameter_data = self.service_parameter_data[0] return dbutils.create_test_service_parameter(**parameter_data) def _create_db_objects(self, data_set=None): if not data_set: data_set = self.service_parameter_data data = [] for parameter_data in data_set: data.append(self._create_db_object(parameter_data)) return data def get_one(self, uuid, expect_errors=False, error_message=None): response = self.get_json(self.get_single_url(uuid), headers=self.API_HEADERS) self.validate_response(response, expect_errors, error_message, json_response=True) return response def get_list(self): response = self.get_json(self.API_PREFIX, headers=self.API_HEADERS) return response[self.RESULT_KEY] def patch(self, uuid, data, expect_errors=False, error_message=None): response = self.patch_dict(self.get_single_url(uuid), data=data, expect_errors=expect_errors, headers=self.API_HEADERS) self.validate_response(response, expect_errors, error_message) if expect_errors: return response else: return response.json def post(self, data, expect_errors=False, error_message=None): formatted_data = self.format_data(data) response = self.post_json(self.API_PREFIX, params=formatted_data, expect_errors=expect_errors, headers=self.API_HEADERS) self.validate_response(response, expect_errors, error_message) if expect_errors: return response else: return response.json[self.RESULT_KEY][0] def apply(self, service, expect_errors=False): data = {} data['service'] = service response = self.post_json(self.API_PREFIX + "/apply", params=data, expect_errors=expect_errors, headers=self.API_HEADERS) return response def validate_response(self, response, expect_errors, error_message, json_response=False): if expect_errors: self.assertEqual(http_client.BAD_REQUEST, response.status_int) self.assertEqual('application/json', response.content_type) if error_message: self.assertIn(error_message, response.json['error_message']) elif not json_response: self.assertEqual(http_client.OK, response.status_int) def validate_data(self, input_data, response_data): self.assert_fields(response_data) for key, value in input_data.items(): if key in self.expected_api_fields: self.assertEqual(value, response_data[key]) def format_data(self, data): formatted_data = dict(data) formatted_data.update({'parameters': {data['name']: data['value']}}) for field in self.required_post_fields: if field not in formatted_data: formatted_data[field] = None return formatted_data class ApiServiceParameterPostTestSuiteMixin(ApiServiceParameterTestCaseMixin): def setUp(self): super(ApiServiceParameterPostTestSuiteMixin, self).setUp() def test_create_success(self): # Test creation of object post_object = self.service_parameter_data[0] response = self.post(post_object) self.validate_data(post_object, response) def test_create_invalid_service(self): # Test creation with an invalid service name post_object = dict(self.service_parameter_data[0]) post_object.update({'service': 'not_valid'}) self.post(post_object, expect_errors=True, error_message="Invalid service name") def test_create_wildcard_success(self): # Test creation of a section that allows wildcard parameter names post_object = self.service_parameter_wildcard response = self.post(post_object) self.validate_data(post_object, response) def test_apply_kubernetes_apiserver_oidc_parameters_semantic(self): # applying kubernetes service parameters with no OIDC parameters # this is a valid configuration response = self.apply('kubernetes') self.assertEqual(http_client.NO_CONTENT, response.status_int) # set SERVICE_PARAM_NAME_OIDC_USERNAME_CLAIM. this is an invalid config # valid configs are (none) # (oidc_issuer_url, oidc_client_id, oidc_username_claim) # (the previous 3 plus oidc_groups_claim) post_object = self.service_parameter_data[3] response = self.post(post_object) self.validate_data(post_object, response) response = self.apply('kubernetes', expect_errors=True) self.assertEqual(http_client.BAD_REQUEST, response.status_int) # the other 2 valid configs post_object = self.service_parameter_data[4] response = self.post(post_object) self.validate_data(post_object, response) post_object = self.service_parameter_data[5] response = self.post(post_object) self.validate_data(post_object, response) response = self.apply('kubernetes') self.assertEqual(http_client.NO_CONTENT, response.status_int) post_object = self.service_parameter_data[6] response = self.post(post_object) self.validate_data(post_object, response) response = self.apply('kubernetes') self.assertEqual(http_client.NO_CONTENT, response.status_int) class ApiServiceParameterDeleteTestSuiteMixin(ApiServiceParameterTestCaseMixin): """ Tests deletion. Typically delete APIs return NO CONTENT. python2 and python3 libraries may return different content_type (None, or empty json) when NO_CONTENT returned. """ def setUp(self): super(ApiServiceParameterDeleteTestSuiteMixin, self).setUp() self.delete_object = self._create_db_object() # Delete an object and ensure it is removed def test_delete(self): # Delete the API object uuid = self.delete_object.uuid response = self.delete(self.get_single_url(uuid), headers=self.API_HEADERS) self.assertEqual(response.status_code, http_client.NO_CONTENT) # Verify the object is no longer returned results = self.get_list() returned_uuids = (result.uuid for result in results) self.assertNotIn(uuid, returned_uuids) class ApiServiceParameterListTestSuiteMixin(ApiServiceParameterTestCaseMixin): """ list operations """ def test_empty_list(self): results = self.get_list() self.assertEqual([], results) def test_single_entry(self): # create a single object single_object = self._create_db_object() uuid = single_object.uuid response = self.get_json(self.get_single_url(uuid)) self.validate_data(single_object, response) def test_many_entries_in_list(self): db_obj_list = self._create_db_objects() response = self.get_list() # Verify that the input data is found in the result response_map = {} for api_object in response: response_map[api_object['uuid']] = api_object for db_oject in db_obj_list: self.validate_data(db_oject, response_map[db_oject.uuid]) class ApiServiceParameterPatchTestSuiteMixin(ApiServiceParameterTestCaseMixin): def setUp(self): super(ApiServiceParameterPatchTestSuiteMixin, self).setUp() self.patch_object = self._create_db_object() def test_patch_valid(self): # Update value of patchable field new_data = {'value': '8077'} response = self.patch(self.patch_object.uuid, new_data) # Verify that the attribute was updated self.patch_object.update(new_data) self.validate_data(self.patch_object, response) def test_patch_invalid_value(self): # Pass a value that fails a semantic check when patched by the API new_data = {'value': 'a_string'} self.patch(self.patch_object.uuid, new_data, expect_errors=True, error_message="must be an integer value") def test_patch_wildcard_success(self): # Test modification of a section that allows wildcard parameter names wildcard_object = self._create_db_object(self.service_parameter_wildcard) new_data = {'value': 'UDPv4'} response = self.patch(wildcard_object.uuid, new_data) wildcard_object.update(new_data) self.validate_data(wildcard_object, response) class PlatformIPv4ControllerApiServiceParameterDeleteTestCase(ApiServiceParameterDeleteTestSuiteMixin, base.FunctionalTest, dbbase.ProvisionedControllerHostTestCase): pass class PlatformIPv4ControllerApiServiceParameterListTestCase(ApiServiceParameterListTestSuiteMixin, base.FunctionalTest, dbbase.ProvisionedControllerHostTestCase): pass class PlatformIPv4ControllerApiServiceParameterPostTestCase(ApiServiceParameterPostTestSuiteMixin, base.FunctionalTest, dbbase.ProvisionedControllerHostTestCase): pass class PlatformIPv4ControllerApiServiceParameterPatchTestCase(ApiServiceParameterPatchTestSuiteMixin, base.FunctionalTest, dbbase.ProvisionedControllerHostTestCase): pass