def __init__(self, metadata_container, configuration, strategy, requirements=None, skip_list=None): """ :type metadata_container: cts_core.metadata.metadata_container.MetadataContainer :type configuration: cts_framework.configuration.configuration.Configurations :type strategy: cts_core.validation.patch.patching_strategy.PatchingStrategy :type requirements: list """ self._metadata_container = metadata_container self._api_caller = ApiCaller(configuration) self._strategy = strategy self._preconditions = Preconditions(metadata_container, requirements) self._fast_mode = getenv('CTS_PATCH_ONE_PER_TYPE') self._types_patched = set() self._skip_list = skip_list if self._fast_mode: cts_warning( "Test results may be UNRELIABLE - PATCHING ONLY ONE RESOURCE OF EACH TYPE!" ) self._endpoints_and_keys_to_ignore = [] if self._skip_list: cts_warning( "Test results may be UNRELIABLE - Some elements presented on REST API will be IGNORED!" ) self._endpoints_and_keys_to_ignore = self._load_ignore_list_file( skip_list)
def run(self): if self.metadata_container is None: return test_name = "Storage Services CRUD test" print "MESSAGE::%s starting" % test_name print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover(MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.STORAGE_SERVICE, min=1), Requirement(MetadataConstants.LOGICAL_DRIVE_COLLECTION, min=1), Requirement(MetadataConstants.REMOTE_TARGET_COLLECTION, min=1), Requirement(MetadataConstants.LOGICAL_DRIVE, min=2) ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) if status == ValidationStatus.FAILED: self.set_status_blocked() return self.api_caller = ApiCaller(self.configuration) status = ValidationStatus.join_statuses(status, self.crud_logical_drive()) status = ValidationStatus.join_statuses(status, self.crud_remote_target()) print "MESSAGE::%s overall status: %s" % (test_name, ColorPrinter.format_status(status))
def _check_endpoint_connection(cls, can_run, configuration): if can_run: service_root = ApiCaller(configuration) link, kwargs = service_root._build_request( configuration.ApiEndpoint) url = link.link[:link.link.rfind("/") + 1] + "redfish/v1" response, status_code = service_root._do_request( kwargs, url, "Get") if status_code in range(400, 600): cts_error("{url:id} Get failed. Status code: {code}", url=url, code=status_code) try: cts_error( '{code} : {description}. Please check your configuration.', code=status_code, description=ErrorReturnCodes.ErrorCodes[status_code]) except KeyError: pass return False else: return True else: return False
def run(self): try: json_loader = JsonLoader() except OSError: print('ERROR:: No proper action found. Please reinstall a CTS') return if self.metadata_container is None: return if not self.configuration.UseCase: json_loader.print_available_actions() return try: actions_to_take = json_loader.get_use_case( self.configuration.UseCase) specific_action = json_loader.get_use_case_action( actions_to_take['filepath']) except (IndexError, TypeError): json_loader.print_available_actions() print('ERROR:: The selected action was not found. Exiting..') return print('TEST_CASE::API crawling') api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover( MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) configuration = self.configuration self.api_caller = ApiCaller(configuration) print('TEST_CASE::Action Runner') status = self.action_runner(specific_action) print('STATUS::{status}'.format(status=status))
def __init__(self, metadata_container, configuration): """ :type metadata_container: cts_core.metadata.metadata_container.MetadataContainer :type configuration: """ self._metadata_container = metadata_container self._api_caller = ApiCaller(configuration) self._config_property_reader = configuration self._status = ValidationStatus.UNKNOWN self._discovery_container = None self._bfs_queue = []
class ApiCallerPayloadUnitTest(unittest.TestCase): def setUp(self): configuration = Configuration(**dict(ApiEndpoint=API_ENDPOINT)) self.api_caller = ApiCaller(configuration) def test_payload_given(self): params, kwargs = self.api_caller._build_request(RESOURCE, payload=PAYLOAD) self.assertIn("data", kwargs) self.assertEqual(kwargs["data"], json.dumps(PAYLOAD)) def test_payload_not_given(self): params, kwargs = self.api_caller._build_request(RESOURCE) self.assertNotIn("data", kwargs)
def run(self): if self.metadata_container is None: return self.chosen_endpoint, self.chosen_zone, self.chosen_volume, self.chosen_volume_collection = (None,)*4 self.initiator_endpoint, self.target_endpoint, self.zone_endpoint, self.created_volume = (None,)*4 test_name = "Storage Services CRUD test" print "MESSAGE::%s starting" % test_name print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover(MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.STORAGE_SERVICE, min=1), Requirement(MetadataConstants.VOLUME, min=1), Requirement(MetadataConstants.VOLUME_COLLECTION, min=1) ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) if status == ValidationStatus.FAILED: self.set_status_failed() return self.api_caller = ApiCaller(self.configuration) status = ValidationStatus.join_statuses(status, self.crud_fabrics_target()) print "MESSAGE::%s overall status: %s" % (test_name, ColorPrinter.format_status(status))
def test_certs_given(self): configuration = Configuration(**dict(CertificateCertFile=CERT_FILE, CertificateKeyFile=CERT_KEY, ApiEndpoint=API_ENDPOINT)) params, kwargs = ApiCaller(configuration)._build_request(RESOURCE) self.assertIn("cert", kwargs) self.assertEqual(kwargs["cert"], (CERT_FILE, CERT_KEY))
def test_basic_auth_given(self): configuration = Configuration(**dict(User=USER, Password=PASSWORD, ApiEndpoint=API_ENDPOINT)) params, kwargs = ApiCaller(configuration)._build_request(RESOURCE) self.assertIn("auth", kwargs) self.assertEqual(kwargs["auth"], (USER, PASSWORD))
def run(self): if self.metadata_container is None: return print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover( MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) self.api_caller = ApiCaller(self.configuration) self.crud_vlan()
def run(self): if self.metadata_container is None: return self.chosen_endpoint, self.chosen_zone, self.chosen_volume, self.chosen_volume_collection = ( None, ) * 4 self.initiator_endpoint, self.target_endpoint, self.zone_endpoint, self.created_volume = ( None, ) * 4 self.initiator_unique_name = self.generate_unique_names() if self.configuration.UniqueInitiatorName \ != "generate" else self.configuration.UniqueInitiatorName self.target_unique_name = self.generate_unique_names() if self.configuration.UniqueTargetName \ != "generate" else self.configuration.UniqueTargetName test_name = "Storage Services CRUD test with NVM Express (NVMe) Support" print "MESSAGE::%s starting" % test_name print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover( MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.STORAGE_SERVICE, min=1), Requirement(MetadataConstants.VOLUME, min=1), Requirement(MetadataConstants.VOLUME_COLLECTION, min=1) ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) if status == ValidationStatus.FAILED: self.set_status_failed() return self.api_caller = ApiCaller(self.configuration) status = ValidationStatus.join_statuses(status, self.crud_nvme()) print "MESSAGE::%s overall status: %s" % ( test_name, ColorPrinter.format_status(status))
def __init__(self, metadata_container, configuration, strategy, requirements=None): """ :type metadata_container: cts_core.metadata.metadata_container.MetadataContainer :type configuration: cts_framework.configuration.configuration.Configurations :type strategy: cts_core.validation.patch.patching_strategy.PatchingStrategy :type requirements: list """ self._metadata_container = metadata_container self._api_caller = ApiCaller(configuration) self._strategy = strategy self._preconditions = Preconditions(metadata_container, requirements) self._fast_mode = getenv('CTS_PATCH_ONE_PER_TYPE') self._types_patched = set() if self._fast_mode: cts_warning( "Test results may be UNRELIABLE - PATCHING ONLY ONE RESOURCE OF EACH TYPE!" )
def run(self): json_loader = JsonLoader() if self.metadata_container is None: return if not self.configuration.UseCase: json_loader.print_available_actions() return actions_to_take = json_loader.get_use_case(self.configuration.UseCase) specific_action = json_loader.get_use_case_action( actions_to_take['filepath']) print('TEST_CASE::API crawling') api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover( MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) configuration = self.configuration self.api_caller = ApiCaller(configuration) print('TEST_CASE::Action Runner') status = self.action_runner(specific_action) print('STATUS::{status}'.format(status=status))
def run(self): if self.metadata_container is None: return print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover(MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) self.api_caller = ApiCaller(self.configuration) self.crud_vlan()
def run(self): json_loader = JsonLoader() if self.metadata_container is None: return if not self.configuration.UseCase: json_loader.print_available_actions() return actions_to_take = json_loader.get_use_case(self.configuration.UseCase) specific_action = json_loader.get_use_case_action(actions_to_take['filepath']) print('TEST_CASE::API crawling') api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover(MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) configuration = self.configuration self.api_caller = ApiCaller(configuration) print('TEST_CASE::Action Runner') status = self.action_runner(specific_action) print('STATUS::{status}'.format(status=status))
def test_key_given_cert_not(self): configuration = Configuration(**dict(CertificateKeyFile=CERT_KEY, ApiEndpoint=API_ENDPOINT)) params, kwargs = ApiCaller(configuration)._build_request(RESOURCE) self.assertNotIn("cert", kwargs)
class ApiCallerCallsUnitTest(unittest.TestCase): def setUp(self): configuration = Configuration(**dict(ApiEndpoint=API_ENDPOINT)) self.api_caller = ApiCaller(configuration) def test_get_on_empty_resource(self): with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 200 response.headers = {} response.text = "" requests_get_mock.return_value = response link, status, status_code, response_body, headers = \ self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertFalse(status) def test_incorrect_status_code(self): with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 500 response.headers = {} response.text = "{}" requests_get_mock.return_value = response link, status, status_code, response_body, headers = \ self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertFalse(status) def test_request_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.RequestException() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn("ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.RequestException'>:;", output.raw) def test_connection_error_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.ConnectionError() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn("ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.ConnectionError'>:;", output.raw) def test_http_error_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.HTTPError() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn("ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.HTTPError'>:", output.raw) def test_url_required_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.URLRequired() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn("ERROR::Get url=http://1.2.3.4:567/resource Error <class " "'requests.exceptions.URLRequired'>:", output.raw) def test_to_many_redirects_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.TooManyRedirects() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn("ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.TooManyRedirects'>:;", output.raw) def test_timeout_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.Timeout() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn("ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.Timeout'>:;", output.raw) def test_incorrect_body(self): with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 200 response.headers = {} response.text = "not a json" requests_get_mock.return_value = response with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn( "ERROR", output.raw) def test_no_content(self): with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 204 response.headers = {} response.text = None requests_get_mock.return_value = response link, status, status_code, response_body, headers = \ self.api_caller.get_resource("/resource", DiscoveryContainer(), acceptable_return_codes = [204]) self.assertTrue(status) self.assertEqual(status_code, 204) self.assertEqual(response_body, dict()) response.json.assert_not_called() def test_async_create_without_location(self): with mock.patch('requests.post') as requests_post_mock: response = Mock() response.status_code = ReturnCodes.ACCEPTED response.headers = {} response.text = None requests_post_mock.return_value = response with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 204 response.headers = {} response.text = None requests_get_mock.return_value = response with StdoutCapture() as output: status, status_code, response_body, headers = self.api_caller.post_resource('odata_id', DiscoveryContainer(), payload={}) self.assertIn('Location header not found', '\n'.join(output)) def test_unexpected_async_post_response(self): with mock.patch('requests.post') as requests_get_mock: response = Mock() response.status_code = ReturnCodes.ACCEPTED response.headers = {} response.text = "{}" requests_get_mock.return_value = response with self.assertRaises(AsyncOperation): self.api_caller.post_resource('odata_id', DiscoveryContainer(), payload={}, wait_if_async=False) def test_async_post_response(self): with mock.patch('requests.post') as requests_post_mock: not_done = Mock() not_done.status_code = ReturnCodes.ACCEPTED not_done.headers = {'Location' : 'location'} not_done.text = None requests_post_mock.return_value = not_done with mock.patch('requests.get') as requests_get_mock: done = Mock() done.status_code = ReturnCodes.OK done.headers = {'Location' : 'location'} done.text = "{ \"done\": true }" requests_get_mock.side_effect = [not_done, not_done, done] with StdoutCapture() as output: status, status_code, response_body, headers = self.api_caller.post_resource('odata_id', DiscoveryContainer(), payload={}) self.assertTrue(response_body['done']) def test_resource_in_discovery_container_after_get_patch_delete(self): with mock.patch('requests.get') as requests_get_mock: resource = {"@odata.id": "odata.id", "something": "irrelevant"} get_response = Mock() get_response.status_code = ReturnCodes.OK get_response.headers = {} get_response.text = json.dumps(resource) requests_get_mock.return_value = get_response discovery_container = DiscoveryContainer() self.api_caller.get_resource("/resource", discovery_container) self.assertEqual(discovery_container["http://{API_ENDPOINT}/resource".format( API_ENDPOINT=API_ENDPOINT)].body, resource) patched_resource = {"@odata.id": "odata.id", "something": "relevant"} get_response.text = json.dumps(patched_resource) with mock.patch('requests.patch') as requests_patch_mock: patch_response = Mock() patch_response.status_code = ReturnCodes.OK patch_response.headers = {} patch_response.text = "{}" requests_patch_mock.return_value = patch_response _, _, _, _ = self.api_caller.patch_resource("/resource", discovery_container) self.assertEqual(discovery_container["http://{API_ENDPOINT}/resource".format( API_ENDPOINT=API_ENDPOINT)].body, patched_resource) with mock.patch('requests.delete') as requests_delete_mock: delete_response = Mock() delete_response.status_code = ReturnCodes.NO_CONTENT delete_response.headers = {} delete_response.text = "" requests_delete_mock.return_value = delete_response _, _, _, _ = self.api_caller.delete_resource("/resource", discovery_container) self.assertNotIn("/resource", discovery_container) def test_get_xml(self): with mock.patch('requests.get') as get: response = Mock() response.status_code = ReturnCodes.OK response.headers = {} response.text = "<xml></xml>" get.return_value = response link, status, status_code, response_body, headers = self.api_caller.get_xml("uri") self.assertEqual("<xml></xml>", response_body)
def test_ssl_default(self): configuration = Configuration(**dict(ApiEndpoint=API_ENDPOINT)) url, kwargs = ApiCaller(configuration)._build_request(RESOURCE) self.assertEqual(url, Link("http://%s%s" % (API_ENDPOINT, RESOURCE), API_ENDPOINT))
class CRUDOperations(CtsTestScript): TIMEOUT = 600 DESCRIPTION = """test script validating CRUD (create, read, update and delete) functionalities""" def run(self): if self.metadata_container is None: return print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover( MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) self.api_caller = ApiCaller(self.configuration) self.crud_vlan() def _test_case_create_vlan(self, vlan_network_interface_collection): print "TEST_CASE::Create VLAN" status, status_code, response_body, headers = \ self.api_caller.post_resource( vlan_network_interface_collection, self.discovery_container, payload=self.post_vlan_payload) if not status: cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False try: self.created_vlan_network_interface = headers["Location"] self.created_vlan_network_interface_netlock = self.discovery_container.get_netloc( vlan_network_interface_collection) print "MESSAGE::Newly created VLAN Network Interface URI: %s" % self.created_vlan_network_interface except KeyError: cts_error( "Incorrect response - header shall contain the location of the created resource" ) self.set_status_failed() return False print "MESSAGE::VLAN Network Interface created" self.set_status_passed() return True def _test_case_get_created_vlan(self): print "TEST_CASE::Get created VLAN" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_vlan_network_interface, self.discovery_container, api_endpoint_override=self.created_vlan_network_interface_netlock) if not status: cts_error( "Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False else: print "MESSAGE::Comparing newly created VLAN Network Interface to given payload" # PSME is unable to set "Name" and "VLANEnable" fields properly but only "VLANEnable" is mandatory if not JsonComparator.compare_json_to_golden( response_body, self.post_vlan_payload, ignore=["/VLANEnable"]): cts_error( "Newly created VLAN Network Interface's body incorrect") self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface correct" self.set_status_passed() return True def _test_case_delete_created_vlan(self): print "TEST_CASE::Delete created VLAN" status, status_code, response_body, _ = self.api_caller.delete_resource( self.created_vlan_network_interface, self.discovery_container) if not status: cts_error( "Wrong status code of DELETE response: {status_code};" " response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface deleted" self.set_status_passed() return True def _test_case_get_deleted_vlan(self): print "TEST_CASE::Get deleted VLAN" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_vlan_network_interface, self.discovery_container, acceptable_return_codes=[ReturnCodes.NOT_FOUND], api_endpoint_override=self.created_vlan_network_interface_netlock) if not status: cts_error( "Wrong status code of GET response after deletion: {status_code};" " response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface not found (as intended)" self.set_status_passed() return True def crud_vlan(self): """ Test is trying to perform CRUD (create, read, update, delete) operations on a VLAN Network Interface resource """ requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.VLAN_NETWORK_INTERFACE_COLLECTION, min=1), ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) if status is ValidationStatus.PASSED_WITH_WARNINGS: cts_warning( "Without some components it is not possible to perform tests.") print "STATUS::{status}".format(status=status) return ValidationStatus.PASSED_WITH_WARNINGS if status is ValidationStatus.FAILED: return ValidationStatus.BLOCKED vlan_network_interface_collections = \ dict(self.discovery_container.get_resources(MetadataConstants.VLAN_NETWORK_INTERFACE_COLLECTION, any_child_version=True)) chosen_vlan_network_interface_collection = None new_vlan_id = 0 for vlan_network_interface_collection in vlan_network_interface_collections: present_vlan_ids = [] vlan_network_interfaces = \ dict(self.discovery_container. get_resources(MetadataConstants.VLAN_NETWORK_INTERFACE, constraints=[from_collection(vlan_network_interface_collection)], any_child_version=True)) if len(vlan_network_interfaces) > MAX_VLAN_ID - MIN_VLAN_ID: continue for resource_link, resource in vlan_network_interfaces.iteritems(): try: present_vlan_ids.append(resource.body["VLANId"]) except: cts_error("Inorrect resource {resource_link:id} structure", **locals()) for possible_id in range(MIN_VLAN_ID, MAX_VLAN_ID + 1): if possible_id not in present_vlan_ids: chosen_vlan_network_interface_collection = vlan_network_interface_collection new_vlan_id = possible_id break if chosen_vlan_network_interface_collection: break if chosen_vlan_network_interface_collection: print "MESSAGE::CRUD test will be performed on vlan network interface collection %s" % \ chosen_vlan_network_interface_collection else: cts_warning('No free VLAN id available') return ValidationStatus.BLOCKED self.post_vlan_payload = dict( VLANId=new_vlan_id, VLANEnable=True, Oem=dict(Intel_RackScale=dict(Tagged=True))) if self._test_case_create_vlan( chosen_vlan_network_interface_collection): self._test_case_get_created_vlan() self._test_case_delete_created_vlan() self._test_case_get_deleted_vlan()
class CRUDOperations(CtsTestScript): TIMEOUT = 600 DESCRIPTION = """test script validating CRUD (create, read, update and delete) functionalities""" def run(self): if self.metadata_container is None: return test_name = "Storage Services CRUD test" print "MESSAGE::%s starting" % test_name print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover(MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.STORAGE_SERVICE, min=1), Requirement(MetadataConstants.LOGICAL_DRIVE_COLLECTION, min=1), Requirement(MetadataConstants.REMOTE_TARGET_COLLECTION, min=1), Requirement(MetadataConstants.LOGICAL_DRIVE, min=2) ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) if status == ValidationStatus.FAILED: self.set_status_blocked() return self.api_caller = ApiCaller(self.configuration) status = ValidationStatus.join_statuses(status, self.crud_logical_drive()) status = ValidationStatus.join_statuses(status, self.crud_remote_target()) print "MESSAGE::%s overall status: %s" % (test_name, ColorPrinter.format_status(status)) def _test_case_create_logical_drive(self, logical_drive_collection, payload=None): print "TEST_CASE::Create a logical drive" if not payload: payload = self.post_logical_drive_payload status, status_code, response_body, headers = self.api_caller.post_resource( logical_drive_collection, self.discovery_container, payload=payload) if not status: cts_error("Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED try: self.created_logical_drive = headers["Location"] self.created_logical_drive_netloc = \ self.discovery_container.get_netloc(logical_drive_collection) print "MESSAGE::Newly created logical drive url %s" % self.created_logical_drive except KeyError: cts_error("In header shall be provided location to created resource") self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Logical drive created" self.set_status_passed() return ValidationStatus.PASSED def _test_case_get_created_logical_drive(self, logical_drive): print "TEST_CASE::Get the created logical drive" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_logical_drive, self.discovery_container, api_endpoint_override=self.created_logical_drive_netloc) if not status: cts_error("Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED else: print "MESSAGE::Comparing newly created logical drive to given payload" if not JsonComparator.compare_json_to_golden(response_body, self.expected_created_logical_drive_body, ignore = ["/Name"]): # Name setting not supported by PSME REST API cts_error("Newly created logical drive's body incorrect") self.set_status_failed() print "MESSAGE::Proceeding with deleting the resource" return ValidationStatus.FAILED print "MESSAGE::Newly created logical drive's correct" self.set_status_passed() return ValidationStatus.PASSED def _test_case_update_created_logical_drive(self): print "TEST_CASE::Update the created logical drive" patch_logical_drive_payload = dict( Bootable = not self.post_logical_drive_payload["Bootable"] ) status, status_code, response_body, _ = self.api_caller.patch_resource(self.created_logical_drive, self.discovery_container, payload=patch_logical_drive_payload) if not status: cts_error("Error while executing PATCH; status code: {status_code}; response body: " "{response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_logical_drive, self.discovery_container, api_endpoint_override=self.created_logical_drive_netloc) if not status: cts_error("Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED else: print "MESSAGE::Verifying whether the logical drive's update has taken place" if not JsonComparator.compare_json_to_golden(response_body["Bootable"], patch_logical_drive_payload["Bootable"]): cts_error("Newly created logical drive could not be updated") self.set_status_failed() print "MESSAGE::Proceeding with deleting the resource" return ValidationStatus.FAILED print "MESSAGE::Newly created logical drive updated correctly" self.set_status_passed() return ValidationStatus.PASSED def _test_case_delete_created_logical_drive(self, logical_drive): print "TEST_CASE::Delete the created logical drive" status, status_code, response_body, _ = self.api_caller.delete_resource(self.created_logical_drive, self.discovery_container) if not status: cts_error("Wrong status code of DELETE response: {status_code}; response body: " "{response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Newly created logical drive deleted" self.set_status_passed() return ValidationStatus.PASSED def _test_case_get_deleted_logical_drive(self, logical_drive): print "TEST_CASE::Get the deleted logical drive" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_logical_drive, self.discovery_container, acceptable_return_codes=[ReturnCodes.NOT_FOUND], api_endpoint_override=self.created_logical_drive_netloc) if not status: cts_error("Wrong status code of GET response after deletion: {status_code}; response body: " "{response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Newly created logical drive not found (as intended)" self.set_status_passed() return ValidationStatus.PASSED def crud_logical_drive(self, create_logical_drive_for_target_crud=None): """ Test is trying to perform CRUD (create, read, update, delete) operations on a logical drive :type create_logical_drive_for_target_crud: Boolean """ status = ValidationStatus.BLOCKED lvm_found = False chosen_logical_drive_type, chosen_logical_drive_mode = None, None chosen_logical_drive_collection, chosen_logical_drive, chosen_logical_drive_group = None, None, None inherited_bootable = None # to speed things up, always look for the lowest capacity logical drive LVM_LV_SPECIFIC_CONSTRAINTS = [equal("Mode", "LV"), greater("CapacityGiB", 0), not_equal("Snapshot", True), order_by("CapacityGiB")] CEPH_RBD_SPECIFIC_CONSTRAINTS = [equal("Mode", "RBD"), greater("CapacityGiB", 0), order_by("CapacityGiB")] logical_drive_collections = dict(self.discovery_container.get_resources( MetadataConstants.LOGICAL_DRIVE_COLLECTION, any_child_version=True)) for collection in logical_drive_collections.keys(): chosen_logical_drive_collection = collection logical_drive_groups = dict(self.discovery_container.get_resources(MetadataConstants.LOGICAL_DRIVE, any_child_version=True, constraints=[equal("Mode", "LVG"), from_collection(collection)])) if logical_drive_groups: lvm_found = True else: logical_drive_groups = dict(self.discovery_container.get_resources(MetadataConstants.LOGICAL_DRIVE, any_child_version=True, constraints=[equal("Mode", "Pool"), from_collection( collection)])) if logical_drive_groups: specific_constraints = LVM_LV_SPECIFIC_CONSTRAINTS if lvm_found else CEPH_RBD_SPECIFIC_CONSTRAINTS for lvg_link, lvg in logical_drive_groups.iteritems(): chosen_logical_drive_group = lvg.odata_id try: child_lvs = [lv["@odata.id"] for lv in lvg.body["Links"]["LogicalDrives"]] chosen_logical_drive, _ = self.discovery_container.get_resources( MetadataConstants.LOGICAL_DRIVE, any_child_version=True, constraints=specific_constraints + [odata_ids(child_lvs)])[0] lv = self.discovery_container[chosen_logical_drive] inherited_bootable = lv.body["Bootable"] chosen_logical_drive_type = lv.body["Type"] chosen_logical_drive_mode = lv.body["Mode"] except (KeyError,IndexError) as error: cts_error("Exception while parsing {id:id}: {error:exception}", id=chosen_logical_drive, error=error) chosen_logical_drive = None pass else: break if not chosen_logical_drive_group: resource = "logical drive group" if lvm_found else "Pool" cts_warning("Could not find a {resource} resource in collection {collection}", **locals()) if lvm_found and not chosen_logical_drive: cts_warning("Could not find a logical volume in collection {collection}", **locals()) if lvm_found and not chosen_logical_drive: cts_error("Could not find any logical volume") return status if not chosen_logical_drive_group: cts_error("Could not find any logical drive group, aborting") return status self.post_logical_drive_payload = dict( Name="LVM Logical Drive created by CTS", Type=chosen_logical_drive_type, Mode=chosen_logical_drive_mode, Protected=False, CapacityGiB=self.discovery_container[chosen_logical_drive].body["CapacityGiB"], Image="CTS test image", Bootable=inherited_bootable if inherited_bootable else False, Snapshot=True, Links=dict( LogicalDrives=[{"@odata.id": chosen_logical_drive_group}], MasterDrive={"@odata.id": urlparse(chosen_logical_drive).path} ) ) # Creating the first RBD if no other was found if not chosen_logical_drive: if lvm_found: print "ERROR::Could not find a logical drive suitable for snapshot" return status cts_warning("Could not find any logical drive suitable for snapshot. Since CEPH was detected, CTS will "\ "try to create the first RBD") self.post_logical_drive_payload = dict( Name="CEPH Logical Drive created by CTS", Type="CEPH", Mode="RBD", Protected=False, CapacityGiB=self.discovery_container[chosen_logical_drive_group].body["CapacityGiB"]/2, Bootable=False, Snapshot=False, Links=dict( LogicalDrives=[{"@odata.id": urlparse(chosen_logical_drive_group).path}], MasterDrive=None ) ) print "MESSAGE::Logical drive CRUD test will be performed on logical drive collection %s, creating logical " \ "drive based on %s on group %s" % (chosen_logical_drive_collection, chosen_logical_drive, chosen_logical_drive_group) self.expected_created_logical_drive_body = deepcopy(self.post_logical_drive_payload) # The logical drive in LogicalDrives link is really the volume group and is going to be seen under # UsedBy on REST self.expected_created_logical_drive_body["Links"]["UsedBy"] = \ self.expected_created_logical_drive_body["Links"]["LogicalDrives"] self.expected_created_logical_drive_body["Links"]["LogicalDrives"] = [] status = self._test_case_create_logical_drive(chosen_logical_drive_collection) if status == ValidationStatus.PASSED: # only perform other tests if creation was successful ld = self.created_logical_drive status = ValidationStatus.join_statuses(status, self._test_case_get_created_logical_drive(ld)) if create_logical_drive_for_target_crud: return status if status == ValidationStatus.PASSED: status = ValidationStatus.join_statuses(status, self._test_case_update_created_logical_drive()) status = ValidationStatus.join_statuses(status, self._test_case_delete_created_logical_drive(ld)) status = ValidationStatus.join_statuses(status, self._test_case_get_deleted_logical_drive(ld)) return status def _test_case_create_remote_target(self, remote_target_collection): print "TEST_CASE::Create a remote target" status, status_code, response_body, headers = self.api_caller.post_resource( remote_target_collection, self.discovery_container, payload=self.post_remote_target_payload) if not status: cts_error("Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED try: self.created_remote_target = headers["Location"] self.created_remote_target_netloc = \ self.discovery_container.get_netloc(remote_target_collection) print "MESSAGE::Newly created remote target url %s" % self.created_remote_target except KeyError: cts_error("In header shall be provided location to created resource") self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Remote target created" self.set_status_passed() return ValidationStatus.PASSED def _test_case_get_created_remote_target(self): print "TEST_CASE::Get the created remote target" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_remote_target, self.discovery_container, api_endpoint_override=self.created_remote_target_netloc) if not status: cts_error("Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED else: print "MESSAGE::Comparing newly created remote target to given payload" if not JsonComparator.compare_json_to_golden(response_body, self.post_remote_target_payload): cts_error("Newly created remote target's body incorrect") self.set_status_failed() print "MESSAGE::Proceeding with deleting the resource" return ValidationStatus.FAILED print "MESSAGE::Newly created remote target's body correct" self.set_status_passed() return ValidationStatus.PASSED def _test_case_logical_drive_has_link_to_created_target(self, logical_drive): print "TEST_CASE::Check for a link to the created target in logical drive's body" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(logical_drive, self.discovery_container) if not status: cts_error("Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED else: try: target = urlparse(self.created_remote_target).path if not {"@odata.id": target} in response_body["Links"]["Targets"]\ and not {"@odata.id": target + "/"} in response_body["Links"]["Targets"]: cts_error("No link to the created target in the logical drive's body") self.set_status_failed() return ValidationStatus.FAILED except KeyError: cts_error("No Links/Targets array present in the logical drive's body") self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::A link from the logical drive to the target is present" self.set_status_passed() return ValidationStatus.PASSED def _test_case_update_created_remote_target(self): print "TEST_CASE::Update the created remote target" patch_remote_target_payload = dict( Initiator=[dict( iSCSI=dict( InitiatorIQN="cts.initiator:patched_cts_initiator" ) )] ) status, status_code, response_body, _ = self.api_caller.patch_resource(self.created_remote_target, self.discovery_container, payload=patch_remote_target_payload) if not status: cts_error("Error while executing PATCH; status code: {status_code}; response body: " "{response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_remote_target, self.discovery_container, api_endpoint_override=self.created_remote_target_netloc) if not status: cts_error("Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED else: print "MESSAGE::Verifying whether the remote target's update has taken place" if not JsonComparator.compare_json_to_golden(response_body["Initiator"], patch_remote_target_payload["Initiator"]): cts_error("Newly created remote target could not be updated") self.set_status_failed() print "MESSAGE::Proceeding with deleting the resource" return ValidationStatus.FAILED print "MESSAGE::Newly created remote target updated correctly" self.set_status_passed() return ValidationStatus.PASSED def _test_case_delete_created_remote_target(self): print "TEST_CASE::Delete the created remote target" status, status_code, response_body, _ = self.api_caller.delete_resource(self.created_remote_target, self.discovery_container) if not status: cts_error("Wrong status code of DELETE response: {status_code}; response body: " "{response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Newly created remote target deleted" self.set_status_passed() return ValidationStatus.PASSED def _test_case_get_deleted_remote_target(self): print "TEST_CASE::Get the deleted remote target" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_remote_target, self.discovery_container, acceptable_return_codes=[ReturnCodes.NOT_FOUND], api_endpoint_override=self.created_remote_target_netloc) if not status: cts_error("Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Newly created remote target not found (as intended)" self.set_status_passed() return ValidationStatus.PASSED def crud_remote_target(self): """ Test is checking for rsa compute blade presence """ ld_created_for_crud = False target_iqn = "cts.target:cts_test_target" target_iqns = set() chosen_logical_drive, chosen_remote_target_collection = None, None if not self.discovery_container.count_resources(MetadataConstants.LOGICAL_DRIVE, any_child_version=True): cts_error("Insufficient resources for API test. No logical drives found.") return ValidationStatus.BLOCKED logical_drives = dict(self.discovery_container.get_resources(MetadataConstants.LOGICAL_DRIVE, any_child_version=True, constraints=[equal("Mode", "LV"), equal("Protected", False)])) if not logical_drives: logical_drives = dict(self.discovery_container.get_resources(MetadataConstants.LOGICAL_DRIVE, any_child_version=True, constraints=[equal("Mode", "RBD"), equal("Protected", False)])) try: chosen_remote_target_collection = self.discovery_container.get_resources( MetadataConstants.REMOTE_TARGET_COLLECTION, any_child_version=True)[0][0] chosen_logical_drive = [link for link, resource in logical_drives.iteritems() if not len(resource.body["Links"]["Targets"])][0] except (KeyError, IndexError): pass if not chosen_remote_target_collection: cts_error("Insufficient resources available on API for test. Could not find any remote target collection") return ValidationStatus.BLOCKED if not chosen_logical_drive: cts_warning("Insufficient resources available on API for test. Could not find an unprotected logical drive " "with no targets attached") print "MESSAGE::Trying to create a new logical volume for the target, then proceeding with remote target "\ "CRUD test" if self.crud_logical_drive(create_logical_drive_for_target_crud=True) == ValidationStatus.PASSED: chosen_logical_drive = self.created_logical_drive ld_created_for_crud = True else: cts_error("Creating a new logical volume for the target failed, skipping remote target tests") return ValidationStatus.BLOCKED targets = [link for link, resource in self.discovery_container.get_resources(MetadataConstants.REMOTE_TARGET, any_child_version=True, constraints=[ from_collection(chosen_remote_target_collection) ])] for target in targets: addresses = self.discovery_container[target].body["Addresses"] for address in addresses: try: target_iqns.add(address["iSCSI"]["TargetIQN"]) except KeyError: pass if target_iqn in target_iqns: iqn_number = 0 while target_iqn + str(iqn_number) in target_iqns: iqn_number += 1 target_iqn = target_iqn + str(iqn_number) print "MESSAGE::Remote Target CRUD test will be performed on remote target collection %s, creating remote "\ "target based on %s with first lowest consecutive \'cts.target:cts_test_target<number>\' target IQN "\ "available: %s" % (chosen_remote_target_collection, chosen_logical_drive, target_iqn) self.post_remote_target_payload = dict( Addresses=[dict( iSCSI=dict( TargetIQN=target_iqn, TargetLUN=[dict( LUN=1, LogicalDrive={"@odata.id": urlparse(chosen_logical_drive).path} )], ) )], Initiator=[dict( iSCSI=dict( InitiatorIQN="cts.initiator:initiator_cts_test" ) )] ) status = self._test_case_create_remote_target(chosen_remote_target_collection) if status == ValidationStatus.PASSED: # only perform other tests if creation was successful status = ValidationStatus.join_statuses(status, self._test_case_get_created_remote_target()) status = ValidationStatus.join_statuses(status, self._test_case_logical_drive_has_link_to_created_target( chosen_logical_drive)) status = ValidationStatus.join_statuses(status, self._test_case_update_created_remote_target()) status = ValidationStatus.join_statuses(status, self._test_case_delete_created_remote_target()) status = ValidationStatus.join_statuses(status, self._test_case_get_deleted_remote_target()) if ld_created_for_crud: ld = self.created_logical_drive del_status = self._test_case_delete_created_logical_drive(ld) del_status = ValidationStatus.join_statuses(del_status, self._test_case_get_deleted_logical_drive(ld)) if status == ValidationStatus.PASSED and del_status != status: cts_error("Remote target CRUD test passed, but the logical drive it created failed to delete properly") status = ValidationStatus.PASSED_WITH_WARNINGS return status
class ValidateUseCase(CtsTestScript): TIMEOUT = 600 DESCRIPTION = """test case validating api responses against provided use case""" CONFIGURATION_PARAMETERS = [ TestScript.ConfigurationParameter(parameter_name="UseCase", parameter_description="Use Case name", parameter_type=str, is_required=False, parameter_default_value=[]) ] def run(self): json_loader = JsonLoader() if self.metadata_container is None: return if not self.configuration.UseCase: json_loader.print_available_actions() return actions_to_take = json_loader.get_use_case(self.configuration.UseCase) specific_action = json_loader.get_use_case_action(actions_to_take['filepath']) print('TEST_CASE::API crawling') api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover(MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) configuration = self.configuration self.api_caller = ApiCaller(configuration) print('TEST_CASE::Action Runner') status = self.action_runner(specific_action) print('STATUS::{status}'.format(status=status)) def action_runner(self, specific_action): actions_status = ValidationStatus.RUNNING for action_step, action in enumerate(specific_action['actions'], 1): step_counter = "%d/%d" % (action_step, len(specific_action['actions'])) action_type = action['type'] if action_type == 'timeout': try: self.perform_timeout(step_counter, action['value']) except IndexError: self.perform_timeout(step_counter) continue retry_if_fail, retry_timeout = self.__get_retry_information(action) action_args = (specific_action['name'], action['name'], action['action'], step_counter, int(retry_if_fail), int(retry_timeout)) if action_type == 'external': actions_status = ValidationStatus.join_statuses(self.perform_external_command(*action_args)) continue elif action_type == 'usecase': actions_status = ValidationStatus.join_statuses(self.perform_action(*action_args)) return actions_status @staticmethod def perform_timeout(step_counter, timeout=1): cts_message("Step: %s" % step_counter) cts_message("Timeout: %s" % timeout) sleep(float(timeout)) @CaseInformation def perform_external_command(self, test_name, case_name, action, step_counter, retry=0, timeout=0): """ Perform command on external host :param test_name: !! used by decorator :param case_name: !! used by decorator :param action: :param step_counter: !! used by decorator :param retry: :param timeout: :return: """ # CONNECTION PHASE response_from_remote_host = self._connection_phase(action) # VERIFICATION PHASE print(response_from_remote_host) verification_status = self._verification_phase(action, raw_response=response_from_remote_host) if not verification_status: return ValidationStatus.FAILED return ValidationStatus.PASSED @staticmethod def run_commands_on_host(commands, credentials): responses_from_ssh_target = [] with ExternalCommandOverSSH(**credentials) as host: for cmd in commands: _, stdout, stderr = host.exec_command(cmd) err = stderr.read() if err is not '': cts_error(err) responses_from_ssh_target.append(stdout.read()) return responses_from_ssh_target[-1] def _connection_phase(self, action): """ Do actions on remote and return last response """ tgt_credential = action['target_credential'] command = action['command'] first_target = { "target_hostname": tgt_credential['hostname'], "target_username": tgt_credential['username'], "target_password": tgt_credential['password'], "target_port": tgt_credential['port'] } try: second_tgt_credential = action['jump_target_credential'] second_target = { "second_hostname": second_tgt_credential['hostname'], "second_username": second_tgt_credential['username'], "second_password": second_tgt_credential['password'], "second_port": second_tgt_credential['port'] } except KeyError: return self.run_commands_on_host(command, first_target) return self.run_commands_on_host(command, self.__merge_dicts(first_target, second_target)) def _verification_phase(self, action, raw_response=None): external_command = action['response']['command'] expected_response = action['response']['result'] command_type, command_location = self.__verify_command_type(external_command) command_args = self.__verify_command_args(external_command) external_action = ExecuteExternalScript(script_path=command_location, raw_response=raw_response, expected_result=expected_response) if not external_action(**command_args): cts_error('Script response IS NOT equal to excepted.') return False cts_message('Script response is equal to excepted.') return True @CaseInformation def perform_action(self, test_name, case_name, action, step_counter, retry=0, timeout=0): case_action = CaseAction(**action) cts_message("_____") cts_message("{name}: {step}\n\t\t {description}".format(name=test_name, step=step_counter, description=case_name)) cts_message("{request_method}: {request}".format(request_method=action['request_method'], request=action['request'].replace("{", "(").replace("}", ")"))) resource_type, resource_id, resource_action = self._get_target_information(case_action.request) discovered_systems = self.discovery_container.get_resources(resource_type, any_child_version=True) if not discovered_systems: cts_error('Cannot find %s' % resource_type) return ValidationStatus.BLOCKED try: computer_odata_id = self.discovery_container.get(discovered_systems[resource_id][0]).odata_id except IndexError: cts_error('Cannot find %s' % resource_type) return ValidationStatus.BLOCKED action_endpoint = '{odata_id}/{action_name}'.format(odata_id=computer_odata_id, action_name='/'.join(resource_action)) status, _, _, _ = self.api_caller.post_resource(action_endpoint, self.discovery_container, case_action.payload, acceptable_return_codes=case_action.response) if (not status) and (retry > 0): cts_message("Operation failed, left retry %s" % retry) cts_message("Timeout: %s" % timeout) sleep(float(timeout)) self.perform_action(test_name, case_name, action, step_counter, retry=(retry - 1), timeout=timeout) if not status: cts_error('Cannot execute action %s' % action_endpoint) return ValidationStatus.FAILED cts_message('End of: %s' % case_name) return ValidationStatus.PASSED def _get_target_information(self, request): request_cleaned = request.replace('{', '').replace('}', '').split('/') resource_type = request_cleaned[0] resource_id = self._choose_resource(request_cleaned[1]) action = request_cleaned[2:] return resource_type, resource_id, action @staticmethod def _choose_resource(number): return 0 if number == 'any' else int(number) @staticmethod def __verify_command_type(external_cmd): command_type, command_location = (None,) * 2 try: command_type = external_cmd[0] if command_type.lower() != 'python': cts_message('CTS support only Python external scripts') return False command_location = external_cmd[1] except IndexError: cts_error('Missing verification command details') return command_type, command_location def __verify_command_args(self, external_cmd): command_args = None try: command_args = self.__generate_dict_from_custom(external_cmd[2:]) except IndexError: pass return command_args @staticmethod def __get_retry_information(action): """ :param action: :return: retry_if_fail, retry_timeout """ retry_if_fail, retry_timeout = 0, 1 try: retry_if_fail, retry_timeout = action['action']['retry_if_fail'], \ action['action']['retry_timeout'] except KeyError: pass return retry_if_fail, retry_timeout # HELPERS @staticmethod def __generate_dict_from_custom(list_values): dict_from_list = {} for element in list_values: k, v = element.split(":") dict_from_list[k] = v return dict_from_list @staticmethod def __merge_dicts(a, b): c = a.copy() c.update(b) return c
class CRUDOperations(CtsTestScript): TIMEOUT = 600 DESCRIPTION = """test script validating CRUD (create, read, update and delete) functionalities""" def run(self): if self.metadata_container is None: return print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover( MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.ETHERNET_SWITCH, min=1), Requirement(MetadataConstants.ETHERNET_SWITCH_PORT, min=1) ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) self.api_caller = ApiCaller(self.configuration) self.crud_acl_rule() self.crud_vlan() self.crud_static_mac() def _test_case_create_acl(self, acl_collection): print "TEST_CASE::Create ACL" self.created_acl_netloc = self.discovery_container.get_netloc( acl_collection) status, status_code, response_body, headers = self.api_caller.post_resource( acl_collection, self.discovery_container, payload=self.post_acl_payload, api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False try: self.created_acl = headers["Location"] print "MESSAGE::Newly created ACL URI: %s" % self.created_acl except KeyError: cts_error( "Incorrect response - header shall contain the location of the created resource" ) self.set_status_failed() return False if self.created_acl.endswith("/"): self.created_acl += self.created_acl[:-1] print "MESSAGE::ACL created" self.set_status_passed() return True def _test_case_get_created_acl(self): print "TEST_CASE::Get created ACL" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_acl, self.discovery_container, api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False else: print "MESSAGE::Comparing newly created ACL to given payload" if not JsonComparator.compare_json_to_golden( response_body, self.post_acl_payload): cts_error("Newly created ACL's body incorrect") self.set_status_failed() return False print "MESSAGE::Newly created ACL correct" self.set_status_passed() return True def _test_case_delete_created_acl(self): print "TEST_CASE::Delete created ACL" status, status_code, response_body, _ = self.api_caller.delete_resource( self.created_acl, self.discovery_container) if not status: cts_error( "Wrong status code of DELETE response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created ACL deleted" self.set_status_passed() return True def _test_case_get_deleted_acl(self): print "TEST_CASE::Get deleted ACL" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_acl, self.discovery_container, acceptable_return_codes=[ ReturnCodes.NOT_FOUND], api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of GET response after deletion: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created ACL not found (as intended)" self.set_status_passed() return True def _test_case_bind_acl_to_port(self, port): print "TEST_CASE::Bind ACL to a port" port_id = dict() port_id["@odata.id"] = port payload = dict(Port=port_id) status, status_code, response_body, _ = \ self.api_caller.post_resource(self.created_acl + "/Actions/EthernetSwitchACL.Bind", self.discovery_container, payload=payload, acceptable_return_codes= [ReturnCodes.NO_CONTENT], expect_location=False, api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::ACL successfully bound to port" self.set_status_passed() return True def _test_case_unbind_acl_from_port(self, port): print "TEST_CASE::Unbind ACL from a port" port_id = dict() port_id["@odata.id"] = port payload = dict(Port=port_id) status, status_code, response_body, _ = \ self.api_caller.post_resource(self.created_acl + "/Actions/EthernetSwitchACL.Unbind", self.discovery_container, payload=payload, acceptable_return_codes=[ReturnCodes.NO_CONTENT], expect_location=False, api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::ACL successfully unbound from port" self.set_status_passed() return True def _test_case_bound_acl_and_port_are_linked(self, port, should_be_linked): print "TEST_CASE::Check if the ACL and port are linked " + \ ("(should be)" if should_be_linked else "(shouldn't be)") port = urlparse(port).path acl = urlparse(self.created_acl).path acl_linked, port_linked = None, None try: link, status, status_code, acl_body, _ = \ self.api_caller.get_resource(self.created_acl, self.discovery_container, api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False if not {"@odata.id": port} in acl_body["Links"]["BoundPorts"] \ and not {"@odata.id": port + "/"} in acl_body["Links"]["BoundPorts"]: acl_linked = False if should_be_linked: cts_error("No link to the bound port in the ACL's body") else: print "MESSAGE::No link to the bound port in the ACL's body (as expected)" else: acl_linked = True if should_be_linked: print "MESSAGE::ACL has a link to the bound Port" else: cts_error("ACL still has a link to the unbound Port") except KeyError: cts_error("No Links/BoundPorts array present in the ACL's body") try: link, status, status_code, port_body, _ = \ self.api_caller.get_resource(port, self.discovery_container, api_endpoint_override=self.discovery_container.get_netloc( port)) if not status: cts_error( "Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False if not {"@odata.id": acl} in port_body["Links"]["ActiveACLs"] \ and not {"@odata.id": acl + "/"} in port_body["Links"]["ActiveACLs"]: port_linked = False if should_be_linked: cts_error("No link to the bound ACL in the port's body") else: print "MESSAGE::No link to the bound ACL in the port's body (as expected)" else: port_linked = True if should_be_linked: print "MESSAGE::Port has a link to the bound ACL" else: cts_error("Port still has a link to the unbound ACL") except KeyError: cts_error("No Links/ActiveACLs array present in the port's body") if acl_linked is not None and port_linked is not None: if acl_linked == port_linked == should_be_linked: self.set_status_passed() return True self.set_status_failed() return False def _test_case_add_rules_to_acl(self): print "TEST_CASE::Add rules to the ACL" self.created_rules = dict() optional_rules_triggered_an_error = False rule_collection = self.created_acl + "/Rules" for rule_type, rule_payload in self.rules_payloads.iteritems(): print "MESSAGE::Adding a {type} rule".format(type=rule_type) status, status_code, response_body, headers = \ self.api_caller.post_resource( rule_collection, self.discovery_container, payload=rule_payload, api_endpoint_override=self.created_acl_netloc) if not status: if rule_type not in self.mandatory_rules: optional_rules_triggered_an_error = True cts_warning( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) cts_warning( "Rule {rule_type} was defined as optional, continuing", **locals()) continue cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False try: self.created_rules[rule_type] = headers["Location"] print "MESSAGE::Newly created ACL Rule URI: %s" % self.created_acl except KeyError: cts_error( "Incorrect response - header shall contain the location of the created resource" ) self.set_status_failed() return False print "MESSAGE::Getting the newly created ACL Rule URI: %s" % self.created_acl link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_rules[rule_type], self.discovery_container, api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False else: print "MESSAGE::Comparing newly created ACL Rule to given payload" if not JsonComparator.compare_json_to_golden( response_body, rule_payload): cts_error("Newly created Rule's body incorrect") self.set_status_failed() return False else: print "MESSAGE::Newly created Rule's body correct" print "MESSAGE::Rule added" print "MESSAGE::ACL Rules created succesfully" if optional_rules_triggered_an_error: self.set_status_passed_with_warnings() else: self.set_status_passed() return True def _test_case_delete_rules_from_acl(self): print "TEST_CASE::Delete the created rules from the ACL" for rule_type, rule_uri in self.created_rules.iteritems(): print "MESSAGE::Deleting {type}".format(type=rule_type) status, status_code, response_body, _ = self.api_caller.delete_resource( rule_uri, self.discovery_container) if not status: cts_error( "Wrong status code of DELETE response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Checking if the rule was actually deleted" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(rule_uri, self.discovery_container, acceptable_return_codes=[ ReturnCodes.NOT_FOUND], api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of GET response after deletion: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::The rule was not found (as intended)" print "MESSAGE::ACL Rules deleted succesfully" self.set_status_passed() return True def crud_acl_rule(self): """ Test is trying to perform CRUD (create, read, update, delete) as well as binding operations on an ACL and ACL Rule resource """ switches = \ dict(self.discovery_container.get_resources(MetadataConstants.ETHERNET_SWITCH, any_child_version=True)) all_ports = [] if not switches: cts_error( "Could not find any Ethernet Switch to perform the test on, aborting" ) return ValidationStatus.BLOCKED chosen_switch, chosen_acl_collection, chosen_port, mirrored_port = None, None, None, None for switch in switches.keys(): acl_collections = dict( self.discovery_container.get_resources( MetadataConstants.ETHERNET_SWITCH_ACL_COLLECTION, any_child_version=True, constraints=[from_collection(switch)])) if acl_collections: chosen_switch = switch chosen_acl_collection = choice(acl_collections.keys()) all_ports = [ port[1].odata_id for port in self.discovery_container.get_resources( MetadataConstants.ETHERNET_SWITCH_PORT, any_child_version=True, constraints=[from_collection(chosen_switch)]) ] try: chosen_port = choice(all_ports) all_ports.remove(chosen_port) mirrored_port = choice(all_ports) except IndexError: if not chosen_port: print "MESSAGE::Could not find a proper port for ACL binding on the switch" continue else: print "WARNING::Only one proper port found, unable to create a Mirror rule" break if not chosen_acl_collection: cts_error( "Could not find any ACL collection on any of the present Ethernet Switches, aborting" ) return ValidationStatus.BLOCKED if not chosen_port: cts_error( "Could not find any proper port for ACL binding on any of the present Ethernet Switches, aborting" ) return ValidationStatus.BLOCKED print "MESSAGE:CRUD test will be performed on ACL collection: {collection}, the test will attempt to bind and "\ "unbind the ACL from the port: {port}".format(collection=chosen_acl_collection, port=chosen_port) self.post_acl_payload = dict() self.rules_payloads = dict() self.rules_payloads["Deny"] = dict( Action="Deny", Condition=dict(MACSource=dict(MACAddress="AA:BB:CC:DD:EE:FF", Mask="FF:FF:FF:00:00:00"))) self.rules_payloads["Permit"] = dict( Action="Permit", Condition=dict(IPSource=dict(IPv4Address="192.168.0.1", Mask="255.255.255.0"))) self.rules_payloads["Forward"] = dict( Action="Forward", ForwardMirrorInterface={"@odata.id": chosen_port}, Condition=dict(IPDestination=dict(IPv4Address="1.1.1.1"))) if mirrored_port: self.rules_payloads["Mirror"] = dict( RuleId=1111, Action="Mirror", ForwardMirrorInterface={"@odata.id": chosen_port}, MirrorPortRegion=[{ "@odata.id": mirrored_port }], MirrorType="Egress", Condition=dict(VLANId=dict(Id=1, Mask=None))) # basic rules (Deny and Permit actions) should be supported by any PSME service; therefore failure in adding # them triggers FAILED status for the test. If the mandatory rules are added successfully, but there is an # error while adding the optional (Forward and Mirror actions) rules, the test's result is PASSED WITH WARNINGS self.mandatory_rules = ["Deny", "Permit"] if self._test_case_create_acl(chosen_acl_collection): self._test_case_get_created_acl() if self._test_case_add_rules_to_acl( ): # Rules need to be added before binding if self._test_case_bind_acl_to_port(chosen_port): if self._test_case_bound_acl_and_port_are_linked( chosen_port, True): if self._test_case_unbind_acl_from_port(chosen_port): self._test_case_bound_acl_and_port_are_linked( chosen_port, False) self._test_case_delete_rules_from_acl() self._test_case_delete_created_acl() self._test_case_get_deleted_acl() def _test_case_create_vlan(self, vlan_network_interface_collection): print "TEST_CASE::Create VLAN" status, status_code, response_body, headers = \ self.api_caller.post_resource( vlan_network_interface_collection, self.discovery_container, payload=self.post_vlan_payload) if not status: cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False try: self.created_vlan_network_interface = headers["Location"] self.created_vlan_network_interface_netlock = self.discovery_container.get_netloc( vlan_network_interface_collection) print "MESSAGE::Newly created VLAN Network Interface URI: %s" % self.created_vlan_network_interface except KeyError: cts_error( "Incorrect response - header shall contain the location of the created resource" ) self.set_status_failed() return False print "MESSAGE::VLAN Network Interface created" self.set_status_passed() return True def _test_case_get_created_vlan(self): print "TEST_CASE::Get created VLAN" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_vlan_network_interface, self.discovery_container, api_endpoint_override=self.created_vlan_network_interface_netlock) if not status: cts_error( "Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False else: print "MESSAGE::Comparing newly created VLAN Network Interface to given payload" # PSME is unable to set "Name" and "VLANEnable" fields properly but only "VLANEnable" is mandatory if not JsonComparator.compare_json_to_golden( response_body, self.post_vlan_payload, ignore=["/VLANEnable"]): cts_error( "Newly created VLAN Network Interface's body incorrect") self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface correct" self.set_status_passed() return True def _test_case_delete_created_vlan(self): print "TEST_CASE::Delete created VLAN" status, status_code, response_body, _ = self.api_caller.delete_resource( self.created_vlan_network_interface, self.discovery_container) if not status: cts_error( "Wrong status code of DELETE response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface deleted" self.set_status_passed() return True def _test_case_get_deleted_vlan(self): print "TEST_CASE::Get deleted VLAN" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_vlan_network_interface, self.discovery_container, acceptable_return_codes=[ReturnCodes.NOT_FOUND], api_endpoint_override=self.created_vlan_network_interface_netlock) if not status: cts_error( "Wrong status code of GET response after deletion: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface not found (as intended)" self.set_status_passed() return True def crud_vlan(self): """ Test is trying to perform CRUD (create, read, update, delete) operations on a VLAN Network Interface resource """ requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.ETHERNET_SWITCH, min=1), Requirement(MetadataConstants.ETHERNET_SWITCH_PORT, min=1), Requirement(MetadataConstants.VLAN_NETWORK_INTERFACE_COLLECTION, min=1), ] preconditions = Preconditions(self.metadata_container, requirements) if preconditions.validate( self.discovery_container) == ValidationStatus.FAILED: return ValidationStatus.BLOCKED vlan_network_interface_collections = \ dict(self.discovery_container.get_resources(MetadataConstants.VLAN_NETWORK_INTERFACE_COLLECTION, any_child_version=True)) chosen_vlan_network_interface_collection = None new_vlan_id = 0 for vlan_network_interface_collection in vlan_network_interface_collections: present_vlan_ids = [] vlan_network_interfaces = dict( self.discovery_container.get_resources( MetadataConstants.VLAN_NETWORK_INTERFACE, constraints=[ from_collection(vlan_network_interface_collection) ], any_child_version=True)) if len(vlan_network_interfaces) > MAX_VLAN_ID - MIN_VLAN_ID: continue for resource_link, resource in vlan_network_interfaces.iteritems(): try: present_vlan_ids.append(resource.body["VLANId"]) except: cts_error("Inorrect resource {resource_link:id} structure", **locals()) for possible_id in range(MIN_VLAN_ID, MAX_VLAN_ID + 1): if possible_id not in present_vlan_ids: chosen_vlan_network_interface_collection = vlan_network_interface_collection new_vlan_id = possible_id break if chosen_vlan_network_interface_collection: break if chosen_vlan_network_interface_collection: print "MESSAGE::CRUD test will be performed on vlan network interface collection %s" % \ chosen_vlan_network_interface_collection else: cts_warning('No free VLAN id available') return ValidationStatus.BLOCKED self.post_vlan_payload = dict( VLANId=new_vlan_id, VLANEnable=True, Oem=dict(Intel_RackScale=dict(Tagged=True))) if self._test_case_create_vlan( chosen_vlan_network_interface_collection): self._test_case_get_created_vlan() self._test_case_delete_created_vlan() self._test_case_get_deleted_vlan() def _test_case_create_static_mac(self, static_mac_collection): print "TEST_CASE::Create Static MAC" status, status_code, response_body, headers = self.api_caller.post_resource( static_mac_collection, self.discovery_container, payload=self.post_static_mac_payload) if not status: cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False try: self.created_static_mac = headers["Location"] self.created_static_mac_netloc = self.discovery_container.get_netloc( static_mac_collection) print "MESSAGE::Newly created Static MAC URI: %s" % self.created_static_mac except KeyError: cts_error( "Incorrect response - header shall contain the location of the created resource" ) self.set_status_failed() return False print "MESSAGE::Static MAC created" self.set_status_passed() return True def _test_case_get_created_static_mac(self): print "TEST_CASE::Get created Static MAC" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_static_mac, self.discovery_container, api_endpoint_override=self.created_static_mac_netloc) if not status: cts_error( "Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False else: print "MESSAGE::Comparing newly created Static MAC to given payload" if not JsonComparator.compare_json_to_golden( response_body, self.post_static_mac_payload): cts_error("Newly created Static MAC's body incorrect") self.set_status_failed() return False print "MESSAGE::Newly created Static MAC correct" self.set_status_passed() return True def _test_case_delete_created_static_mac(self): print "TEST_CASE::Delete created Static MAC" status, status_code, response_body, _ = self.api_caller.delete_resource( self.created_static_mac, self.discovery_container) if not status: cts_error( "Wrong status code of DELETE response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created Static MAC deleted" self.set_status_passed() return True def _test_case_get_deleted_static_mac(self): print "TEST_CASE::Get deleted Static MAC" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_static_mac, self.discovery_container, acceptable_return_codes=[ReturnCodes.NOT_FOUND], api_endpoint_override=self.created_static_mac_netloc) if not status: cts_error( "Wrong status code of GET response after deletion: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created Static MAC not found (as intended)" self.set_status_passed() return True def crud_static_mac(self): """ Test is trying to perform CRUD (create, read, update, delete) operations on a Static MAC resource """ def _find_unique_mac_address_vlan_id_pair(present_pairs): MAX_ADDRESS = 281474976710656 - 1 # 16^12 - 1 def address_to_string(address): parts = [] for _ in range(6): parts.append(address % 256) address = address / 256 return ":".join("{0:02x}".format(part) for part in parts).upper() address = 0 vlan_id = MIN_VLAN_ID while (address_to_string(address), vlan_id) in present_pairs: address = address + 1 if address > MAX_ADDRESS: address = 0 vlan_id = vlan_id + 1 if vlan_id > MAX_VLAN_ID: return None return (address_to_string(address), vlan_id) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.ETHERNET_SWITCH, min=1), Requirement(MetadataConstants.ETHERNET_SWITCH_PORT, min=1), Requirement(MetadataConstants.STATIC_MAC_COLLECTION, min=1), ] preconditions = Preconditions(self.metadata_container, requirements) if preconditions.validate( self.discovery_container) == ValidationStatus.FAILED: return ValidationStatus.BLOCKED static_mac_collections = \ dict(self.discovery_container.get_resources(MetadataConstants.STATIC_MAC_COLLECTION, any_child_version=True)) chosen_static_mac_collection = static_mac_collections.keys()[0] print "MESSAGE::\nMESSAGE::Static MAC CRUD test will be performed on collection: {}"\ .format(chosen_static_mac_collection) present_vlan_id_mac_address_pairs = [] for resource_link, resource in self.discovery_container.get_resources( MetadataConstants.STATIC_MAC, any_child_version=True, constraints=[from_collection(chosen_static_mac_collection)]): try: present_vlan_id_mac_address_pairs.append( (resource.body["MACAddress"], resource.body["VLANId"])) except Exception as err: cts_warning( "{link:id} Incorrect resource structure; err={err:exception}", link=resource_link, err=err) initial_values = _find_unique_mac_address_vlan_id_pair( present_vlan_id_mac_address_pairs) if not initial_values: cts_error("Cannot create a Static MAC on the chosen collection - " "all MAC addresses and VLAN ids already occupied") return ValidationStatus.BLOCKED self.post_static_mac_payload = dict(MACAddress=initial_values[0], VLANId=initial_values[1]) # PSME does not support patching Static MAC if self._test_case_create_static_mac(chosen_static_mac_collection): self._test_case_get_created_static_mac() self._test_case_delete_created_static_mac() self._test_case_get_deleted_static_mac()
def test_basic_auth_not_given(self): configuration = Configuration(**dict(ApiEndpoint=API_ENDPOINT)) params, kwargs = ApiCaller(configuration)._build_request(RESOURCE) self.assertNotIn("auth", kwargs)
def setUp(self): configuration = Configuration(**dict(ApiEndpoint=API_ENDPOINT)) self.api_caller = ApiCaller(configuration)
class ApiCallerCallsUnitTest(unittest.TestCase): def setUp(self): configuration = Configuration(**dict(ApiEndpoint=API_ENDPOINT)) self.api_caller = ApiCaller(configuration) @unittest.skip("Temporary change related to bug in Discovery mechanism") def test_get_on_empty_resource(self): with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 200 response.headers = {} response.text = "" requests_get_mock.return_value = response with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn( "ERROR::url=/resource Get failed. Status code: None;", output.raw) def test_empty_service_root(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.ConnectionError() with StdoutCapture() as output: self.api_caller.get_resource("/redfish/v1", DiscoveryContainer()) self.assertIn( "ERROR::Get url=http://1.2.3.4:567/redfish/v1 Error <class 'requests.exceptions.ConnectionError'>:;", output.raw) @unittest.skip("Temporary change related to bug in Discovery mechanism") def test_incorrect_status_code(self): with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 500 response.headers = {} response.text = "{}" requests_get_mock.return_value = response with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn("ERROR::url=/resource Get failed. Status code: 500;", output.raw) def test_request_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.RequestException() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn( "ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.RequestException'>:;", output.raw) def test_connection_error_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.ConnectionError() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn( "ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.ConnectionError'>:;", output.raw) def test_http_error_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.HTTPError() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn( "ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.HTTPError'>:", output.raw) def test_url_required_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.URLRequired() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn( "ERROR::Get url=http://1.2.3.4:567/resource Error <class " "'requests.exceptions.URLRequired'>:", output.raw) def test_to_many_redirects_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.TooManyRedirects() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn( "ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.TooManyRedirects'>:;", output.raw) def test_timeout_exception(self): with mock.patch('requests.get') as requests_get_mock: requests_get_mock.side_effect = requests.Timeout() with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn( "ERROR::Get url=http://1.2.3.4:567/resource Error <class 'requests.exceptions.Timeout'>:;", output.raw) def test_incorrect_body(self): with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 200 response.headers = {} response.text = "not a json" requests_get_mock.return_value = response with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIn("ERROR", output.raw) def test_parse_big_ordered_tree_response(self): from collections import OrderedDict response_payload = OrderedDict( [(u'@odata.context', u'/redfish/v1/$metadata#ServiceRoot.ServiceRoot'), (u'@odata.etag', u'W/"1557488360"'), (u'@odata.id', \ u'/redfish/v1/'), (u'@odata.type', u'#ServiceRoot.v1_3_1.ServiceRoot'), (u'AccountService', OrderedDict([(u'@odata.id', u'/redfish/v1/AccountService')])), (u'Chassis', OrderedDict([(u'@odata.id', u'/redfish/v1/Chassis')])), (u'Description', u'The service root \ for all Redfish requests on this host \ ' ), (u'EventService', OrderedDict([(u' @ odata.id', u' / redfish / v1 / EventService')])), (u'Id', u'RootService'), ( u'Links', OrderedDict( [(u'Oem', OrderedDict([(u'Ami', OrderedDict([(u'@odata.id', u'/redfish/v1/configurations')]))])), (u'Sessions', OrderedDict([(u'@odata.id', u'/redfish/v1/SessionService/Sessions')]))])), ( u'Managers', OrderedDict([(u'@odata.id', u'/redfish/v1/Managers')])), (u'Name', u'PSME Service Root'), ( u'Oem', OrderedDict([(u'Ami', OrderedDict([(u'Chassislocation', OrderedDict( [(u'@odata.id', u'/redfish/v1/Chassislocation')])), (u'Configurations', OrderedDict([(u'@odata.id', u'/redfish/v1/configurations')])), (u'PsmeVersion', u'2.4.181218.tb1')])), (u'Intel_RackScale', OrderedDict([(u'@odata.type', u'#Intel.Oem.ServiceRoot'), (u'ApiVersion', u'2.4.0'), ( u'TelemetryService', OrderedDict([(u'@odata.id', u'/redfish/v1/TelemetryService')]))]))])), (u'Product', u'AMI Redfish Server'), (u'ProtocolFeaturesSupported', OrderedDict([(u'ExpandQuery', OrderedDict( [(u'ExpandAll', True), (u'Levels', True), (u'Links', True), (u'MaxLevels', 5), (u'NoLinks', True)])), (u'FilterQuery', True), (u'SelectQuery', True)])), ( u'RedfishVersion', u'1.5.0'), (u'Registries', OrderedDict([(u'@odata.id', u'/redfish/v1/Registries')])), ( u'SessionService', OrderedDict([(u'@odata.id', u'/redfish/v1/SessionService')])), (u'Systems', OrderedDict([( u'@odata.id', u'/redfish/v1/Systems')])), (u'Tasks', OrderedDict([(u'@odata.id', u'/redfish/v1/TaskService')])), (u'UUID', u'ffffffff-ffff-ffff-ffff-ffffffffffff'), (u'UpdateService', OrderedDict([(u'@odata.id', u'/redfish/v1/UpdateService')]))]) with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 200 response.headers = {} response.text = response_payload requests_get_mock.return_value = response with StdoutCapture() as output: self.api_caller.get_resource("/resource", DiscoveryContainer()) self.assertIsNot("ERROR", output.raw) def test_no_content(self): with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 204 response.headers = {} response.text = None requests_get_mock.return_value = response link, status, status_code, response_body, headers = \ self.api_caller.get_resource("/resource", DiscoveryContainer(), acceptable_return_codes = [204]) self.assertTrue(status) self.assertEqual(status_code, 204) self.assertEqual(response_body, dict()) response.json.assert_not_called() def test_async_create_without_location(self): with mock.patch('requests.post') as requests_post_mock: response = Mock() response.status_code = ReturnCodes.ACCEPTED response.headers = {} response.text = None requests_post_mock.return_value = response with mock.patch('requests.get') as requests_get_mock: response = Mock() response.status_code = 204 response.headers = {} response.text = None requests_get_mock.return_value = response with StdoutCapture() as output: status, status_code, response_body, headers = self.api_caller.post_resource( 'odata_id', DiscoveryContainer(), payload={}) self.assertIn('Location header not found', '\n'.join(output)) def test_unexpected_async_post_response(self): with mock.patch('requests.post') as requests_get_mock: response = Mock() response.status_code = ReturnCodes.ACCEPTED response.headers = {} response.text = "{}" requests_get_mock.return_value = response with self.assertRaises(AsyncOperation): self.api_caller.post_resource('odata_id', DiscoveryContainer(), payload={}, wait_if_async=False) def test_async_post_response(self): with mock.patch('requests.post') as requests_post_mock: not_done = Mock() not_done.status_code = ReturnCodes.ACCEPTED not_done.headers = {'Location': 'location'} not_done.text = None requests_post_mock.return_value = not_done with mock.patch('requests.get') as requests_get_mock: done = Mock() done.status_code = ReturnCodes.OK done.headers = {'Location': 'location'} done.text = "{ \"done\": true }" requests_get_mock.side_effect = [not_done, not_done, done] with StdoutCapture() as output: status, status_code, response_body, headers = self.api_caller.post_resource( 'odata_id', DiscoveryContainer(), payload={}) self.assertTrue(response_body['done']) @unittest.skip("Temporary change related to bug in Discovery mechanism") def test_resource_in_discovery_container_after_get_patch_delete(self): with mock.patch('requests.get') as requests_get_mock: resource = {"@odata.id": "odata.id", "something": "irrelevant"} get_response = Mock() get_response.status_code = ReturnCodes.OK get_response.headers = {} get_response.text = json.dumps(resource) requests_get_mock.return_value = get_response discovery_container = DiscoveryContainer() self.api_caller.get_resource("/resource", discovery_container) self.assertEqual( discovery_container["http://{API_ENDPOINT}/resource".format( API_ENDPOINT=API_ENDPOINT)].body, resource) patched_resource = { "@odata.id": "odata.id", "something": "relevant" } get_response.text = json.dumps(patched_resource) with mock.patch('requests.patch') as requests_patch_mock: patch_response = Mock() patch_response.status_code = ReturnCodes.OK patch_response.headers = {} patch_response.text = "{}" requests_patch_mock.return_value = patch_response _, _, _, _ = self.api_caller.patch_resource( "/resource", discovery_container) self.assertEqual( discovery_container[ "http://{API_ENDPOINT}/resource".format( API_ENDPOINT=API_ENDPOINT)].body, patched_resource) with mock.patch('requests.delete') as requests_delete_mock: delete_response = Mock() delete_response.status_code = ReturnCodes.NO_CONTENT delete_response.headers = {} delete_response.text = "" requests_delete_mock.return_value = delete_response _, _, _, _ = self.api_caller.delete_resource( "/resource", discovery_container) self.assertNotIn("/resource", discovery_container) def test_get_xml(self): with mock.patch('requests.get') as get: response = Mock() response.status_code = ReturnCodes.OK response.headers = {} response.text = "<xml></xml>" get.return_value = response link, status, status_code, response_body, headers = self.api_caller.get_xml( "uri") self.assertEqual("<xml></xml>", response_body)
class ApiExplorer: def __init__(self, metadata_container, configuration): """ :type metadata_container: cts_core.metadata.metadata_container.MetadataContainer :type configuration: """ self._metadata_container = metadata_container self._api_caller = ApiCaller(configuration) self._config_property_reader = configuration self._status = ValidationStatus.UNKNOWN self._discovery_container = None self._bfs_queue = [] def discover(self, url, expected_odata_type, discovery_container=None, api_endpoint_override=None): """ :type discovery_container: cts_core.discovery.discovery_container.DiscoveryContainer :rtype: (cts_core.discovery.discovery_container.DiscoveryContainer, cts_core.validation.validation_status.ValidationStatus) """ self._status = ValidationStatus.PASSED if getenv('CTS_UNPICKLE', None): self._discovery_container = pickle.load( open(environ['CTS_UNPICKLE'], 'rb')) self._discovery_container.metadata_container = self._metadata_container return self._discovery_container, self._status self._discovery_container = discovery_container if discovery_container is not None \ else DiscoveryContainer(metadata_container=self._metadata_container) self._enqueue_resource(url, expected_odata_type, api_endpoint_override) while self._bfs_queue: self._explore_next_resource() if getenv('CTS_PICKLE', None): # workaroud for 'TypeError: can't pickle instancemethod objects' metadata_container_backup = self._discovery_container.metadata_container self._discovery_container.metadata_container = None # do not pickle metadata container pickle.dump(self._discovery_container, open(environ['CTS_PICKLE'], 'wb')) self._discovery_container.metadata_container = metadata_container_backup return self._discovery_container, self._status def _enqueue_resource(self, url, expected_odata_type, api_endpoint_override): self._bfs_queue.append( (url, expected_odata_type, api_endpoint_override)) def _explore_next_resource(self): if self._bfs_queue: url = self._bfs_queue[0][0] expected_odata_type = self._bfs_queue[0][1] api_endpoint_override = self._bfs_queue[0][2] self._bfs_queue.pop(0) self._explore_resource(url, expected_odata_type, api_endpoint_override) def _explore_resource(self, url, expected_odata_type, api_endpoint_override): """ :type url: str """ if url is None: return link = self._api_caller.links_factory.get_resource_link( url, api_endpoint_override=api_endpoint_override) url = link.link if self._discovery_container.is_visited(url): return self._discovery_container.register_url_pattern(url, expected_odata_type) link, status, response_body = self._get_resource( url, api_endpoint_override=api_endpoint_override) try: self._get_members(url, response_body, expected_odata_type, api_endpoint_override) except: pass if not response_body: cts_error("GET {id:id} Empty response body", id=url) self._status = ValidationStatus.FAILED return if status == RequestStatus.SUCCESS and response_body: api_resource = ApiResource(link.link, link.netloc, response_body, expected_odata_type) self._discovery_container.add_resource(api_resource) self._process_resource(api_resource) def _get_members(self, url, response_body, expected_odata_type, api_endpoint_override=None): status = ValidationStatus.BLOCKED link = None if "Members" in response_body: len_members_exists = len(response_body["Members"]) founded_members = [] for founded_member in range(0, len_members_exists): founded_members.append( response_body["Members"][founded_member]["@odata.id"]) for founded_member in list(set(founded_members)): link, status, response_body = self._get_resource( founded_member, api_endpoint_override=api_endpoint_override) if status == RequestStatus.SUCCESS and response_body: api_resource = ApiResource(link.link, link.netloc, response_body, expected_odata_type) self._discovery_container.add_resource(api_resource) self._process_resource(api_resource) if not response_body: cts_error("GET {id:id} Empty response body", id=url) self._status = ValidationStatus.FAILED return if status == RequestStatus.SUCCESS and response_body: api_resource = ApiResource(link.link, link.netloc, response_body, expected_odata_type) self._discovery_container.add_resource(api_resource) self._process_resource(api_resource) def _get_resource(self, url, api_endpoint_override=None): link, status, status_code, response_body, headers = self._api_caller.get_resource( url, self._discovery_container, api_endpoint_override=api_endpoint_override) if status != RequestStatus.SUCCESS: print "ERROR::Error while executing GET %s" % url self._status = ValidationStatus.FAILED status, body = self._dereference_jsonpointer_if_any(url, response_body) return link, status, body def _dereference_jsonpointer_if_any(self, url, response_body): pointer_pos = url.find("#") if pointer_pos != -1: pointer = url[pointer_pos + 1:] if not pointer or pointer[0] != '/': pointer = '/' + pointer try: return RequestStatus.SUCCESS, resolve_pointer( response_body, pointer) except JsonPointerException as exception: cts_error( "JSON pointer exception while dereferencing {path} from {url:id} " "resource; Error: {error}", path=pointer, url=url, error=exception) self._status = ValidationStatus.FAILED return RequestStatus.FAILED, {} return RequestStatus.SUCCESS, response_body def _process_resource(self, api_resource): """ :type api_resource: cts_core.discovery.api_resource.ApiResource """ try: type_definition = self._metadata_container.entities[ api_resource.odata_type] except KeyError as key: cts_error( "{url:id}: Unknown @odata.type {type}. Not able to process sub-elements", url=api_resource.url, type=key) self._status = ValidationStatus.FAILED return try: self._process_properties(api_resource.body, type_definition, url=api_resource.url) except Exception as err: self.status = ValidationStatus.FAILED cts_error("{id:id} Unexpected error {err:exception}", id=api_resource.url, **locals()) def _process_properties(self, json_body, type_definition, url, path=None): if path is None: path = Path() property_description_list = type_definition.all_properties self._append_additional_properties(property_description_list, json_body, type_definition, url, path) for property_description in property_description_list: try: property_body = json_body[property_description.name] except KeyError: # this is discovery phase # any structure errors must be reported during subsequent analysis continue # next property try: if property_description.is_collection: for property_body_member in property_body: self._process_property(property_description, property_body_member, url, path) else: self._process_property(property_description, property_body, url, path) except TypeError: # this is discovery phase # any structure errors must be reported during subsequent analysis pass def _append_additional_properties(self, property_list, body, type_definition, url, path): additionals = set(body.keys()) - set( [property.name for property in property_list]) # filter out special properties (properties that contain @ and #) additionals = filter( lambda property: not is_special_property(property), additionals) if len(additionals) > 0: if type_definition.allow_additional_properties: for property_name in additionals: # process only objects try: property_value = body[property_name] if isinstance(property_value, dict): odata_type = match_dynamic_property( property_name, type_definition) if odata_type is None: try: odata_type = get_odata_type(property_value) except KeyError as key: cts_error( "{url:id}#{path}: @odata.type not found in complex " "additional property", url=url, path=path.append(property_name)) continue adhoc_description = self.ad_hoc_type_definition( property_name, odata_type) property_list.append(adhoc_description) except KeyError: pass else: cts_error( "{url:id}#{path}: Object of type {type}; unexpected properties: [{" "properties}]", type=type_definition.name, properties=", ".join(additionals), url=url, path=path) self._status = ValidationStatus.FAILED def ad_hoc_type_definition(self, property_name, odata_type): raw_soup = """ <Property Name="{name}" Type="{type}"> </Property> """ soup = BeautifulSoup( raw_soup.format(name=property_name, type=odata_type), "lxml").find_all("property")[0] adhoc_description = Property(self._metadata_container, None, soup) return adhoc_description def _process_property(self, property_description, json_body, url, path): if not json_body: return if property_description.type in self._metadata_container.entities.keys( ): return self._process_entity(url, property_description, json_body, path=path.append( property_description.name)) elif property_description.type in self._metadata_container.types.keys( ): if self._metadata_container.types[ property_description. type].type_category == MetadataTypeCategories.COMPLEX_TYPE: return self._process_complex_type( property_description, json_body, url, path=path.append(property_description.name)) else: cts_error("{url:id}#{path} : Unknown type {type}", url=url, path=path, type=property_description.type) def _process_entity(self, url, property_description, body, path): try: odata_id = body[".".join([ODATA, ID])] except KeyError: cts_error( "{odata_id:id}#{path}: @odata.id expected in the resource body", odata_id=odata_id, path=path) return try: if odata_id is not None: netloc = self._discovery_container.get_netloc(url) self._enqueue_resource(odata_id, property_description.type, netloc) except Exception as err: self.status = ValidationStatus.FAILED cts_error( "{odata_id:id} Exception {err:exception}. Not able to process sub-elements", **locals()) self._status = ValidationStatus.FAILED def _process_complex_type(self, property_description, body, url, path): try: # use override @odata.type if present. # At this stage do not verify if override type is consistent with type from property_description; # This will be validated in validate_get_responses test odata_type = get_odata_type(body) except KeyError: odata_type = property_description.type type_definition = self._metadata_container.types[odata_type] self._process_properties(body, type_definition, url, path)
class CRUDOperations(CtsTestScript): TIMEOUT = 600 DESCRIPTION = """test script validating CRUD (create, read, update and delete) functionalities""" def run(self): if self.metadata_container is None: return self.chosen_endpoint, self.chosen_zone, self.chosen_volume, self.chosen_volume_collection = (None,)*4 self.initiator_endpoint, self.target_endpoint, self.zone_endpoint, self.created_volume = (None,)*4 test_name = "Storage Services CRUD test" print "MESSAGE::%s starting" % test_name print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover(MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.STORAGE_SERVICE, min=1), Requirement(MetadataConstants.VOLUME, min=1), Requirement(MetadataConstants.VOLUME_COLLECTION, min=1) ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) if status == ValidationStatus.FAILED: self.set_status_failed() return self.api_caller = ApiCaller(self.configuration) status = ValidationStatus.join_statuses(status, self.crud_fabrics_target()) print "MESSAGE::%s overall status: %s" % (test_name, ColorPrinter.format_status(status)) def crud_fabrics_target(self): status = ValidationStatus.PASSED cts_message("CRUD FABRICS TARGET") # test pre check self.__find_volumes_without_any_links(optional_pre_check=True) try: status = ValidationStatus.join_statuses(status, self._test_create_volume()) except: self._test_delete_all_created_endpoints_and_zones() status = ValidationStatus.FAILED self.__find_and_choose_endpoint_collection() self.__find_and_choose_volume() if not (self.chosen_volume and self.chosen_endpoint and self.chosen_zone): self.set_status_blocked() return ValidationStatus.BLOCKED cts_message("Chosen Volume for test: {volume_url}".format(volume_url=self.chosen_volume)) try: status = ValidationStatus.join_statuses(status, self._test_create_initiator_endpoint()) status = ValidationStatus.join_statuses(status, self._test_create_target_endpoint()) status = ValidationStatus.join_statuses(status, self._test_create_zone_with_only_one_endpoint()) status = ValidationStatus.join_statuses(status, self._test_patch_zone_with_target_endpoint()) except: status = ValidationStatus.FAILED finally: status = ValidationStatus.join_statuses(status, self._test_delete_all_created_endpoints_and_zones()) return status # Test case 1: def _test_create_volume(self): print "TEST_CASE::Create volume" post_volume_payload = dict( CapacityBytes=10000000, CapacitySources=[], AccessCapabilities=[] ) status, status_code, response_body, headers = self.api_caller.post_resource( self.chosen_volume_collection, self.discovery_container, payload=post_volume_payload, acceptable_return_codes=[ReturnCodes.OK, ReturnCodes.ACCEPTED, ReturnCodes.CREATED] ) if not status: cts_error("CTS cannot create volume") self.set_status_failed() return ValidationStatus.FAILED self.created_volume = headers['location'] cts_message("Volume created at {location}".format(location=self.created_volume)) if not self._verify_size_of_created_volume(self.created_volume, post_volume_payload): cts_error("Service create volume with wrong capacity") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 2: def _test_create_initiator_endpoint(self): print "TEST_CASE::Create initiator endpoint" random_suffix = random.randrange(1, RANDOM_RANGE, 1) post_initiator_endpoint_payload = dict( Identifiers=[dict( DurableName="iqn.initiator" + str(random_suffix), DurableNameFormat="iQN" )], ConnectedEntities=[dict( EntityRole="Initiator", )], IPTransportDetails=[], Oem=dict( Intel_RackScale=dict( Authentication=dict( Username="******" + str(random_suffix), Password="******" )))) status, status_code, response_body, headers = self.api_caller.post_resource( self.chosen_endpoint, self.discovery_container, payload=post_initiator_endpoint_payload, acceptable_return_codes=[ReturnCodes.OK, ReturnCodes.ACCEPTED, ReturnCodes.CREATED] ) if not status: cts_error("CTS cannot create Initiator Endpoint") self.set_status_failed() return ValidationStatus.FAILED self.initiator_endpoint = headers['location'] cts_message("Initiator Endpoint created at {location}".format(location=self.initiator_endpoint)) try: verify = self.__verify_created_initiator_endpoint(self.initiator_endpoint, post_initiator_endpoint_payload) except: verify = False if not verify: cts_error("Service create initiator endpoint with wrong value") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 3: def _test_create_target_endpoint(self): print "TEST_CASE::Create target endpoint" selected_volume = self.discovery_container.get(self.chosen_volume) random_suffix = random.randrange(1, RANDOM_RANGE, 1) post_target_endpoint_payload = dict( Identifiers=[dict( DurableName="iqn.initiator" + str(random_suffix), DurableNameFormat="iQN" )], ConnectedEntities=[dict( EntityRole="Target", EntityLink={"@odata.id": selected_volume.odata_id}, )], IPTransportDetails=[], Oem=dict( Intel_RackScale=dict( Authentication=dict( Username="******" + str(random_suffix), Password="******" )))) status, status_code, response, headers = self.api_caller.post_resource( self.chosen_endpoint, self.discovery_container, payload=post_target_endpoint_payload, acceptable_return_codes=[ReturnCodes.OK, ReturnCodes.ACCEPTED, ReturnCodes.CREATED]) if not status: cts_error("CTS can not create Target Endpoint") self.set_status_failed() return ValidationStatus.FAILED self.target_endpoint = headers['location'] cts_message("Target Endpoint created at {location}".format(location=self.target_endpoint)) try: verify = self.__verify_created_target_endpoint(self.target_endpoint, post_target_endpoint_payload) except: verify = False if not verify: cts_error("Service create target endpoint with wrong value") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 4: def _test_create_zone_with_only_one_endpoint(self): print "TEST_CASE::Create zone with only one endpoint" try: initiator_endpoint_link = self.__get_odata_id_links(self.initiator_endpoint) except: cts_error("No valid initiator endpoint for zone creation") self.set_status_blocked() return ValidationStatus.BLOCKED post_create_zone_payload = dict( Links=dict( Endpoints=[{"@odata.id": initiator_endpoint_link}] ) ) status, status_code, _, headers = self.api_caller.post_resource(self.chosen_zone, self.discovery_container, payload=post_create_zone_payload, acceptable_return_codes=[ReturnCodes.ACCEPTED, ReturnCodes.CREATED, ReturnCodes.OK]) if not status: cts_error("CTS can not create Zone") self.set_status_failed() return ValidationStatus.FAILED self.zone_endpoint = headers['location'] cts_message("Zone created at {location}".format(location=self.zone_endpoint)) if not self.__verify_created_zone(self.zone_endpoint, post_create_zone_payload): cts_error("Service zone created with wrong value") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 5: def _test_patch_zone_with_target_endpoint(self): print "TEST_CASE::Patch zone with target endpoint" initiator_or_target_missing = False try: initiator_endpoint_link = self.__get_odata_id_links(self.initiator_endpoint) except: cts_error("No valid initiator endpoint for zone patching") initiator_or_target_missing = True try: target_endpoint_link = self.__get_odata_id_links(self.target_endpoint) except: cts_error("No valid target endpoint for zone patching") initiator_or_target_missing = True if initiator_or_target_missing: cts_error("Missing resources for zone patching") self.set_status_blocked() return ValidationStatus.BLOCKED patch_create_zone_payload = dict( Links=dict( Endpoints=[ {"@odata.id": initiator_endpoint_link}, {"@odata.id": target_endpoint_link}] )) status, status_code, _, headers = self.api_caller.patch_resource(self.zone_endpoint, self.discovery_container, payload=patch_create_zone_payload, acceptable_return_codes=[ReturnCodes.ACCEPTED, ReturnCodes.OK]) if not status: cts_error("CTS can not PATCH Zone") self.set_status_failed() return ValidationStatus.FAILED cts_message("Zone PATCHED at {location}".format(location=self.zone_endpoint)) if not self.__verify_patched_zone(self.zone_endpoint, patch_create_zone_payload): cts_error("Service zone patched with wrong value") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 6: def _test_delete_all_created_endpoints_and_zones(self): status = ValidationStatus.PASSED for resource, link in OrderedDict([("volume", self.created_volume), ("zone", self.zone_endpoint), ("initiator endpoint", self.initiator_endpoint), ("target endpoint", self.target_endpoint)]).iteritems(): print "TEST_CASE::Delete {resource}".format(resource=resource) if link: if not self.__delete_location(link): self.set_status_failed() status = ValidationStatus.join_statuses(status, ValidationStatus.FAILED) else: self.set_status_passed() status = ValidationStatus.join_statuses(status, ValidationStatus.PASSED) else: self.set_status_blocked() status = ValidationStatus.join_statuses(status, ValidationStatus.BLOCKED) return status def __check_resources_have_same_parent_url(self, res1, res2): return (self.discovery_container.get(res1)).parent_url == (self.discovery_container.get(res2)).parent_url def __choose_endpoint_and_zone_from_fabric_collection(self, endpoint_collection, zone_collection): for ec in endpoint_collection: for zc in zone_collection: if self.__check_resources_have_same_parent_url(ec, zc): return ec, zc return None, None def __find_and_choose_volume(self): for volume in self.__find_volumes_without_any_links(): self.chosen_volume = volume break def __delete_location(self, location_url): status, _, _, _ = self.api_caller.delete_resource(location_url, self.discovery_container, acceptable_return_codes=[ReturnCodes.NO_CONTENT]) if not status: cts_error("Resource at %s was not properly deleted" % location_url) return False return self.__verify_resource_non_exist(location_url) def __verify_resource_non_exist(self, location_url_verification): status, _, _, _, _ = self.api_caller.get_resource(location_url_verification, self.discovery_container, acceptable_return_codes=[ReturnCodes.BAD_REQUEST, ReturnCodes.NOT_FOUND]) if not status: cts_error("Resource at %s was not properly deleted" % location_url_verification) return False cts_message("Resource at %s was properly deleted" % location_url_verification) return True def __find_and_choose_endpoint_collection(self): endpoint_collection = dict(self.discovery_container.get_resources( MetadataConstants.ENDPOINT_COLLECTION)) zones_collection = dict(self.discovery_container.get_resources( MetadataConstants.ZONE_COLLECTION)) if not endpoint_collection: cts_error("No Endpoint Collection") self.set_status_blocked() if not zones_collection: cts_error("No Zone Collection") self.set_status_blocked() self.chosen_endpoint, self.chosen_zone = \ self.__choose_endpoint_and_zone_from_fabric_collection( endpoint_collection, zones_collection) def __find_volumes_without_any_links(self, optional_pre_check=False): reusable_volumes = [] logical_drive_collections = dict(self.discovery_container.get_resources( MetadataConstants.VOLUME_COLLECTION, any_child_version=True)) # Allocated Volumes are connected with this same type as Volume volumes_collection_wo_allocated_volumes = [x for x in logical_drive_collections if not "AllocatedVolumes" in x] for collection in volumes_collection_wo_allocated_volumes: if not self.chosen_volume_collection: self.chosen_volume_collection = collection logical_drive_groups = dict(self.discovery_container.get_resources(MetadataConstants.VOLUME, any_child_version=True)) volumes_collection = [] for c in logical_drive_groups.keys(): try: # this Volume we can use to our tests if len(self.discovery_container.get(c).body["Links"]["Oem"]["Intel_RackScale"]["Endpoints"]) == 0: volumes_collection.append(c) except KeyError: pass # if there are any free Volume, suspend test if len(volumes_collection) == 0: if optional_pre_check: return False cts_error("No reusable Volume. Create a Volume without linked Endpoints") self.set_status_blocked() else: reusable_volumes.extend(volumes_collection) return reusable_volumes def __get_odata_id_links(self, link): return (self.discovery_container.get(link)).odata_id # verification code def __verify_patched_zone(self, resource_location, resource_payload): print "MESSAGE::Comparing newly patched zone to given payload" return self.__verify_zone(resource_location, resource_payload) def __verify_created_zone(self, resource_location, resource_payload): print "MESSAGE::Comparing newly created zone to given payload" return self.__verify_zone(resource_location, resource_payload) def __verify_zone(self, resource_location, resource_payload): location_payload_to_compare = JsonComparator.odict_to_dict( self.discovery_container.get(resource_location).body["Links"]["Endpoints"]) payload_to_compare = resource_payload["Links"]["Endpoints"] return JsonComparator.compare_lists(location_payload_to_compare, payload_to_compare) def __verify_created_target_endpoint(self, resource_location, resource_payload): print "MESSAGE::Comparing newly created target endpoint to given payload" location_payload_to_compare = JsonComparator.odict_to_dict( self.discovery_container.get(resource_location).body) payload_to_compare = resource_payload verification_dict = {'ConnectedEntities': ['EntityLink', 'EntityRole'], 'Identifiers': ['DurableNameFormat', 'DurableName']} return self.__verify_body(location_payload_to_compare, payload_to_compare, verification_dict) def __verify_created_initiator_endpoint(self, resource_location, resource_payload): print "MESSAGE::Comparing newly created initiator endpoint to given payload" location_payload_to_compare = JsonComparator.odict_to_dict( self.discovery_container.get(resource_location).body) payload_to_compare = resource_payload verification_dict = {'ConnectedEntities': ['EntityRole'], 'Identifiers': ['DurableNameFormat', 'DurableName']} return self.__verify_body(location_payload_to_compare, payload_to_compare, verification_dict) def _verify_size_of_created_volume(self, resource_location, resource_payload): print "MESSAGE::Comparing newly created volume to given payload" discovery_service_capacity = self.discovery_container.get(resource_location).body["CapacityBytes"] resource_payload_capacity = resource_payload["CapacityBytes"] return self.__verify_bytes(resource_payload_capacity, discovery_service_capacity) @staticmethod def __verify_body(resource_1, resource_2, verification_dict): try: for verification_key in verification_dict.keys(): for verification_elem in verification_dict[verification_key]: if resource_1[verification_key][0][verification_elem] != \ resource_2[verification_key][0][verification_elem]: return False except KeyError: return False return True @staticmethod def __verify_bytes(resource_on_payload, resource_discovered, min_bytes=None, max_bytes=None): if not min_bytes: # default 4 mb min_bytes = 4000000 if not max_bytes: # default 4 mb max_bytes = 4000000 if (resource_on_payload - min_bytes) <= resource_discovered <= (resource_on_payload + max_bytes): return True return False @staticmethod def __verify_strings(resource_on_payload, resource_discovered): resource_on_payload = str(resource_on_payload).lower() resource_discovered = str(resource_discovered).lower() return resource_on_payload == resource_discovered
class MetadataPatchValidator(SkipListMixin, PatchNontrivialPropertyMixin): def __init__(self, metadata_container, configuration, strategy, requirements=None, skip_list=None): """ :type metadata_container: cts_core.metadata.metadata_container.MetadataContainer :type configuration: cts_framework.configuration.configuration.Configurations :type strategy: cts_core.validation.patch.patching_strategy.PatchingStrategy :type requirements: list """ self._metadata_container = metadata_container self._api_caller = ApiCaller(configuration) self._strategy = strategy self._preconditions = Preconditions(metadata_container, requirements) self._fast_mode = getenv('CTS_PATCH_ONE_PER_TYPE') self._types_patched = set() self._skip_list = skip_list if self._fast_mode: cts_warning("Test results may be UNRELIABLE - PATCHING ONLY ONE RESOURCE OF EACH TYPE!") self._endpoints_and_keys_to_ignore = [] if self._skip_list: cts_warning("Test results may be UNRELIABLE - Some elements presented on REST API will be IGNORED!") self._endpoints_and_keys_to_ignore = self._load_ignore_list_file(skip_list) def validate(self, discovery_container): self.discovery_container = discovery_container status = self._check_mandatory_entities() for api_resource in self._enumerate_resources(discovery_container): context = Context(api_resource) resource_status = self._validate_resource(context) self._print_resource_footer(api_resource, context, resource_status) redfish_uri_compliance = discovery_container.validate_redfish_uris_consistency(api_resource, self._metadata_container) status = ValidationStatus.join_statuses(status, resource_status, redfish_uri_compliance) print "SCREEN::Overall status: %s" % status return status @staticmethod def _load_ignore_list_file(path_to_file=None): if not path_to_file: return [] endpoints_to_ignore = {} with open(path_to_file, 'r') as f: for line in f.readlines(): if '::' in line: pre_formatted_value = line.split('::') else: cts_warning("{line} is not proper format, add ::[*] to ignore entire endpoint") continue list_of_endpoints_key = MetadataPatchValidator.__parse_into_a_list(pre_formatted_value[1]) endpoints_to_ignore[pre_formatted_value[0]] = [value for value in list_of_endpoints_key.split(',')] print(endpoints_to_ignore) return endpoints_to_ignore @staticmethod def __parse_into_a_list(text): return text.replace('\n', '').replace('[', '').replace(']', '') def _enumerate_resources(self, discovery_container): count = len(discovery_container) resource_urls = sorted(self.discovery_container.keys()) for idx, url in enumerate(resource_urls): api_resource = discovery_container[url] self._print_progress(api_resource, count, idx) if self.skip_resource(api_resource): continue if self._fast_mode: generalized_id = discovery_container._generalize_url(api_resource.url) if generalized_id in self._types_patched: print "MESSAGE::skipping... other {} has already been tested". \ format(generalized_id) continue else: self._types_patched.add(generalized_id) yield api_resource @staticmethod def _print_progress(api_resource, count, idx): print "SCREEN::" + "-" * 120 progress = "[%5d/%5d]" % (idx + 1, count) print "MESSAGE::%s - %s : %s" % \ (progress, api_resource.odata_id, api_resource.odata_type) def _check_mandatory_entities(self): print "TEST_CASE::Checking for mandatory entities" status = self._preconditions.validate(self.discovery_container) print "STATUS::%s" % status return status def _validate_resource(self, context): """ :type context: Context :rtype: str """ api_resource = context.api_resource if self._metadata_container.to_be_ignored(api_resource.odata_type): return ValidationStatus.PASSED # Load Ignore Elements list and verify. properties_to_skip = [] if api_resource.odata_id in self._endpoints_and_keys_to_ignore: properties_to_skip = self._endpoints_and_keys_to_ignore[api_resource.odata_id] if properties_to_skip[0] == '*': print('MESSAGE::Skipping patching {}. This odata_id is present on list of endpoints to ignore'. format(api_resource.odata_id)) return ValidationStatus.PASSED_WITH_WARNINGS else: print('MESSAGE::This properties from {} will be skipped from patching.'. format(api_resource.odata_id)) print('MESSAGE::Elements are on list to ignore:') for idp, property in enumerate(properties_to_skip, 1): print('MESSAGE::\t{idp}. {property}'.format(idp=idp, property=property)) # do not attempt patch Virtual systems if "SystemType" in api_resource.body and api_resource.body["SystemType"] == "Virtual": print "MESSAGE::Skipping patching of a virtual system {}".format(api_resource.odata_id) return ValidationStatus.PASSED try: properties_list = [property for property in self._metadata_container.entities[api_resource.odata_type].properties.values() if str(property) not in properties_to_skip] try: if len(properties_to_skip): return self._validate_property_list(context, list(), properties_list, properties_to_skip) return self._validate_property_list(context, list(), properties_list) except SystemExit: raise except: cts_error("Unhandled exception {exception:stacktrace} " "while handling resource {odata_id:id}", exception=format_exc(), odata_id=api_resource.odata_id) return ValidationStatus.FAILED except KeyError: cts_error("Unable to find definition of entity type {odata_type}", odata_type=api_resource.odata_type) return ValidationStatus.FAILED def _validate_property_list(self, context, variable_path, property_list, ignore_list=[]): """ :type context: Context :type variable_path: list [str or int] :type property_list: list[cts_core.metadata.model.property.Property] :rtype: str """ api_resource = context.api_resource status = ValidationStatus.PASSED # use local copy - api resource will be modified while test is executed local_property_list = list(property_list) for property_description in local_property_list: if not len(ignore_list) or (len(ignore_list) and str(property_description) not in sum([ignored_property.split("->") for ignored_property in ignore_list], [])): status = ValidationStatus.join_statuses(self._validate_property(context, variable_path, property_description, ignore_list=[ignored_property for ignored_property in ignore_list]), status) continue else: if len([ignored_subproperty for ignored_subproperty in ignored_property.split("->") for ignored_property in ignore_list]) > 1: status = ValidationStatus.join_statuses(self._validate_property(context, variable_path, property_description, ignore_list=[ignored_property.split("->")[1] for ignored_property in ignore_list]), status) else: status = ValidationStatus.join_statuses(self._validate_property(context, variable_path, property_description, ignore_list=[ignored_property for ignored_property in ignore_list]), status) # validate additional properties try: body = api_resource.get_value_from_path(variable_path) except KeyError as key: cts_warning("Unable to access {odataid:id}->{path}. Key={key}", odataid=api_resource.odata_id, path="->".join(variable_path), key=key) body = None if body is not None: all_properties = set(body.keys()) known_properties = set([property.name for property in local_property_list]) additional_properties = all_properties - known_properties for additional_property_name in additional_properties: property_value = body[additional_property_name] if isinstance(property_value, dict) and "@odata.type" in property_value: raw_soup = """ <Property Name="{name}" Type="{type}"> <Annotation Term="OData.Permissions" EnumMember="OData.Permission/ReadWrite"/> </Property> """ soup = BeautifulSoup(raw_soup.format(name=additional_property_name, type=get_odata_type(property_value)), "lxml").find_all("property")[0] adhoc_description = Property(self._metadata_container, None, soup) status = ValidationStatus.join_statuses( self._validate_property(context, variable_path, adhoc_description), status) return status def _validate_property(self, context, variable_path, property_description, skip_collection=False, ignore_list=[]): """ :type context: Context :type variable_path: list [str or int] :type property_description: cts_core.metadata.model.property.Property :type skip_collection: bool :rtype: str """ applicability = None if property_description.patch_status == PatchStatus.NONTRIVIAL: applicability, validation_result = self.handle_nontrivial(context, variable_path, property_description) if applicability == ApplicabilityTestResult.MATCHED: return validation_result if applicability in [ApplicabilityTestResult.NOT_MATCHED, ApplicabilityTestResult.SKIP_PROPERTY]: return validation_result api_resource = context.api_resource if not skip_collection: variable_path = variable_path + [property_description.name] try: property_body = api_resource.get_value_from_path(variable_path) if len(ignore_list) == 1 and ignore_list[0] in property_body: path_s = "->".join([str(segment) for segment in variable_path]) cts_message("Patching %s->%s" % (api_resource.odata_id, path_s)) cts_message("This property {ignore_element} is marked in IgnoredElement list as element to skip".format( ignore_element=ignore_list[0] )) except KeyError as error: if property_description.is_required: path_s = "->".join([str(segment) for segment in variable_path]) print "TEST_CASE::Patching %s->%s" % (api_resource.odata_id, path_s) cts_error("Unable to patch {error}", error=error) status = ValidationStatus.FAILED print "STATUS::%s" % status else: status = ValidationStatus.PASSED return status if property_description.is_collection and not skip_collection: status = ValidationStatus.PASSED for path_element, _ in enumerate(property_body): status = \ ValidationStatus.join_statuses(status, self._validate_property(context, variable_path + [path_element], property_description, skip_collection=True, ignore_list=ignore_list)) return status else: try: if self._metadata_container.types[property_description.type].type_category \ == MetadataTypeCategories.COMPLEX_TYPE: return self._validate_property_list(context, variable_path, self._metadata_container.types[property_description.type]. properties.itervalues(), ignore_list) else: if str(property_description) in ignore_list: return ValidationStatus.PASSED return self._validate_leaf_property(context, variable_path, property_description) except KeyError as key: cts_error("Unable to find definition of type {type} referenced by {odata_id:id}", type=key, odata_id=api_resource.odata_id) return ValidationStatus.FAILED def _validate_leaf_property(self, context, variable_path, property_description): """ :type context: Context :type variable_path: list [str or int] :type property_description: cts_core.metadata.model.property.Property :rtype: str """ if property_description.patch_status in (PatchStatus.NOT_PATCHABLE, PatchStatus.NOT_DEFINED): return ValidationStatus.PASSED if property_description.patch_status == PatchStatus.NONTRIVIAL: applicability, validation_result = self.handle_nontrivial(context, variable_path, property_description) if applicability == ApplicabilityTestResult.MATCHED: return validation_result if applicability in [ApplicabilityTestResult.NOT_MATCHED, ApplicabilityTestResult.SKIP_PROPERTY]: return validation_result return self._validate_patchable_property(context, variable_path, property_description) def _validate_patchable_property(self, context, variable_path, property_description): """ :type context: Context :type variable_path: list [str or int] :type property_description: cts_core.metadata.model.property.Property :rtype: str """ api_resource = context.api_resource verify_only_response = property_description.patch_status == PatchStatus.WRITE_ONLY new_values_list = property_description.generate_values(property_description.annotations) allowed_values = api_resource.get_allowable_from_path(variable_path) if allowed_values: new_values_list = [value for value in new_values_list if value in allowed_values] collection = None if property_description.is_collection and len(variable_path) > 1: collection = api_resource.get_value_from_path(variable_path[:-1]) try: value_pre = current_value = api_resource.get_value_from_path(variable_path) except KeyError: return ValidationStatus.PASSED total_status = ValidationStatus.PASSED for new_value in new_values_list: if new_value != current_value: status, current_value = \ self._patch_and_verify_property(context, variable_path, current_value, new_value, verify_only_response, collection=collection, property_description_type=property_description.name) total_status = ValidationStatus.join_statuses(total_status, status) if current_value is None: try: value_pre = current_value = api_resource.get_value_from_path(variable_path) except KeyError: return total_status if not allowed_values or value_pre in allowed_values: total_status = ValidationStatus.join_statuses(total_status, self._restore_property(context, value_pre, variable_path, collection=collection)) else: print "MESSAGE::Original value '{value_pre}' is illegal. Will not _restore_property" \ .format(value_pre=dumps(value_pre)) return total_status def _patch_and_verify_property(self, context, variable_path, current_value, value_requested, verify_only_response=False, collection=None, property_description_type=None): """ :type context: Context :type variable_path: list [str or int] :type current_value: * :type value_requested: * :type verify_only_response: bool """ property = "%s[%s]" \ % (context.api_resource.odata_id, "->".join([str(path) for path in variable_path])) patch_applied, status, validation_status = self._patch_property(context, property, current_value, value_requested, variable_path, collection=collection) # only _verify_property if the PATCH request succeeded and is not only writeable if status == RequestStatus.SUCCESS and patch_applied: if not verify_only_response: verify_status, current_value = self._verify_property(context, patch_applied, property, current_value, value_requested, variable_path, property_description_type=property_description_type) validation_status = ValidationStatus.join_statuses(validation_status, verify_status) else: validation_status = ValidationStatus.join_statuses(validation_status, ValidationStatus.PASSED) return validation_status, current_value def _patch_property(self, context, property_name, value_pre, value_requested, variable_path, collection=None): """ Issues Patch request to update property to new requested value. Tries to determine if patch has been applied and sets validation status accordingly. :type context: Context :type property_name: str :type value_pre: * :type value_requested: * :type variable_path: list [str or int] :rtype: (bool, bool, str) """ api_resource = context.api_resource print "TEST_CASE:: Patch %s := %s" % (property_name, str(value_requested)) context.register_patch_attempt(property_name, str(value_requested)) data = create_data(variable_path, value_requested, collection) status, status_code, response_body, headers = self._api_caller.patch_resource( api_resource.url, self.discovery_container, payload=data, acceptable_return_codes=self._strategy.allowable_return_codes) patch_applied = self._strategy.was_patch_applied(status_code) if status != RequestStatus.SUCCESS or not patch_applied: validation_status = ValidationStatus.FAILED else: validation_status = ValidationStatus.PASSED print "STATUS::%s" % validation_status return patch_applied, status, validation_status def _verify_property(self, context, patch_applied, property_name, value_pre, value_requested, variable_path, property_description_type): """ Verifies if property value after patch is as expected. Should be equal to original value if patch has not been applied. Should be equal to requested value if patch has been applied. :type context: Context :type patch_applied: bool :type property_name: str :type value_pre: * :type value_requested: * :type variable_path: list[str or int] :rtype: (str, *) """ api_resource = self.discovery_container[context.api_resource.url] # refresh reference to the resource validation_status = ValidationStatus.PASSED print "TEST_CASE::Verify %s (expected: %s)" % (property_name, str(value_requested)) try: value_post = api_resource.get_value_from_path(variable_path) if self._validate_corner_cases(property_description_type, value_post, value_requested) and patch_applied: print "STATUS::%s" % validation_status return validation_status, value_post if value_requested != value_post and patch_applied: cts_error("{odata_id:id} Unexpected value after patching {property} - " + "IS : {post:ignore}, EXPECTED : {requested:ignore}", odata_id=api_resource.odata_id, property=property_name, post=value_post, requested=value_requested) validation_status = ValidationStatus.FAILED elif value_pre != value_post and not patch_applied: cts_error("{odata_id:id} Service reported that the patch has not been applied, " "but '{property}' has changed unexpectedly " "from {pre:ignore} to {post:ignore}", odata_id=api_resource.odata_id, property=property_name, pre=value_pre, post=value_post) validation_status = ValidationStatus.FAILED except ValueNotFound: validation_status = ValidationStatus.FAILED print "STATUS::%s" % validation_status return validation_status, value_post def _restore_property(self, context, value_pre, variable_path, collection=None): """ Final patching that restores original value of the property. :type context: Context :type value_pre: * :type variable_path: list [int or str] :return: str """ api_resource = context.api_resource validation_status = ValidationStatus.PASSED property = "%s" % ("->".join([str(path) for path in variable_path])) print "TEST_CASE::Restore %s := %s" % (property, dumps(value_pre)) data = create_data(variable_path, value_pre, collection) status, status_code, response_body, headers = self._api_caller.patch_resource( api_resource.url, self.discovery_container, payload=data, acceptable_return_codes=self._strategy.allowable_return_codes) if status != RequestStatus.SUCCESS: cts_error("{odata_id:id} Restoring {property} failed. status code {code}", odata_id=api_resource.odata_id, property=property, code=status_code) validation_status = ValidationStatus.FAILED print "STATUS::%s" % validation_status return validation_status def _print_resource_footer(self, api_resource, context, resource_status): if not context.attempted_patches: if resource_status not in [ValidationStatus.PASSED, ValidationStatus.PASSED_WITH_WARNINGS]: # 0 attempted patches means 0 test cases. # FAILED means error (visible in the log) happened during validation, # but not associated with any TEST_CASE # For the purpose of showing failing test case, we produce dummy TEST_CASE here print "TEST_CASE::Patching %s" % api_resource.odata_id print "STATUS::%s" % resource_status else: print "MESSAGE::[{resource} - 0 patchable properties found]". \ format(resource=api_resource.odata_id) def _validate_corner_cases(self, property_description_type, value_post, value_requested): if property_description_type == "MACAddress": return self._validate_canonical_form_of_mac_address(value_post, value_requested) return False def _validate_canonical_form_of_mac_address(self, value_post, value_requested): post = self._mac_address_standarizer(value_post) req = self._mac_address_standarizer(value_requested) return int(post, 16) == int(req, 16) @staticmethod def _mac_address_standarizer(mac_address): replace_dict = {":": "", "-": ""} for i, j in replace_dict.iteritems(): mac_address = mac_address.replace(i, j) return mac_address
class CRUDOperations(CtsTestScript): TIMEOUT = 600 DESCRIPTION = """test script validating CRUD (create, read, update and delete) functionalities""" CONFIGURATION_PARAMETERS = [ TestScript.ConfigurationParameter( parameter_name="UniqueInitiatorName", parameter_description="NVMe Qualified Name (NQN) for Initiator", parameter_type=str, is_required=True, parameter_default_value=""), TestScript.ConfigurationParameter( parameter_name="UniqueTargetName", parameter_description="NVMe Qualified Name (NQN) for Target", parameter_type=str, is_required=True, parameter_default_value="") ] def run(self): if self.metadata_container is None: return self.chosen_endpoint, self.chosen_zone, self.chosen_volume, self.chosen_volume_collection = ( None, ) * 4 self.initiator_endpoint, self.target_endpoint, self.zone_endpoint, self.created_volume = ( None, ) * 4 self.initiator_unique_name, self.target_unique_name = \ self.configuration.UniqueInitiatorName, self.configuration.UniqueTargetName test_name = "Storage Services CRUD test with NVM Express (NVMe) Support" print "MESSAGE::%s starting" % test_name print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover( MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.STORAGE_SERVICE, min=1), Requirement(MetadataConstants.VOLUME, min=1), Requirement(MetadataConstants.VOLUME_COLLECTION, min=1) ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) if status == ValidationStatus.FAILED: self.set_status_failed() return self.api_caller = ApiCaller(self.configuration) status = ValidationStatus.join_statuses(status, self.crud_nvme()) print "MESSAGE::%s overall status: %s" % ( test_name, ColorPrinter.format_status(status)) def crud_nvme(self): status = ValidationStatus.PASSED cts_message("CRUD NVME") # test pre check self.__find_volumes_without_any_links(optional_pre_check=True) try: status = ValidationStatus.join_statuses(status, self._test_create_volume()) except: self._test_delete_all_created_endpoints_and_zones() status = ValidationStatus.FAILED self.__find_and_choose_endpoint_collection() self.__find_and_choose_volume() if not (self.chosen_volume and self.chosen_endpoint and self.chosen_zone): self.set_status_blocked() return ValidationStatus.BLOCKED cts_message("Chosen Volume for test: {volume_url}".format( volume_url=self.chosen_volume)) try: status = ValidationStatus.join_statuses( status, self._test_create_initiator_endpoint()) status = ValidationStatus.join_statuses( status, self._test_create_target_endpoint()) status = ValidationStatus.join_statuses( status, self._test_create_zone_with_only_one_endpoint()) status = ValidationStatus.join_statuses( status, self._test_patch_zone_with_target_endpoint()) except: status = ValidationStatus.FAILED finally: status = ValidationStatus.join_statuses( status, self._test_delete_all_created_endpoints_and_zones()) return status # Test case 1: def _test_create_volume(self): print "TEST_CASE::Create volume" post_volume_payload = dict(CapacityBytes=10000000) status, status_code, response_body, headers = self.api_caller.post_resource( self.chosen_volume_collection, self.discovery_container, payload=post_volume_payload, acceptable_return_codes=[ ReturnCodes.OK, ReturnCodes.ACCEPTED, ReturnCodes.CREATED ]) if not status: cts_error("CTS cannot create volume") self.set_status_failed() return ValidationStatus.FAILED self.created_volume = headers['location'] cts_message("Volume created at {location}".format( location=self.created_volume)) if not self._verify_size_of_created_volume(self.created_volume, post_volume_payload): cts_error("Service create volume with wrong capacity") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 2: def _test_create_initiator_endpoint(self): print "TEST_CASE::Create initiator endpoint" post_initiator_endpoint_payload = dict( Identifiers=[ dict(DurableName=self.initiator_unique_name, DurableNameFormat="NQN") ], IPTransportDetails=[], ConnectedEntities=[dict( EntityRole="Initiator", Identifiers=[], )], Links=dict(Oem=dict(Intel_RackScale=dict()))) status, status_code, response_body, headers = self.api_caller.post_resource( self.chosen_endpoint, self.discovery_container, payload=post_initiator_endpoint_payload, acceptable_return_codes=[ ReturnCodes.OK, ReturnCodes.ACCEPTED, ReturnCodes.CREATED ]) if not status: cts_error("CTS cannot create Initiator Endpoint") self.set_status_failed() return ValidationStatus.FAILED self.initiator_endpoint = headers['location'] cts_message("Initiator Endpoint created at {location}".format( location=self.initiator_endpoint)) try: verify = self.__verify_created_initiator_endpoint( self.initiator_endpoint, post_initiator_endpoint_payload) except: verify = False if not verify: cts_error("Service create initiator endpoint with wrong value") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 3: def _test_create_target_endpoint(self): print "TEST_CASE::Create target endpoint" selected_volume = self.discovery_container.get(self.chosen_volume) post_target_endpoint_payload = dict( Identifiers=[ dict(DurableName=self.target_unique_name, DurableNameFormat="NQN") ], IPTransportDetails=[], ConnectedEntities=[ dict( EntityRole="Target", Identifiers=[], EntityLink={"@odata.id": selected_volume.odata_id}, ) ], Links=dict(Oem=dict(Intel_RackScale=dict()))) status, status_code, response, headers = self.api_caller.post_resource( self.chosen_endpoint, self.discovery_container, payload=post_target_endpoint_payload, acceptable_return_codes=[ ReturnCodes.OK, ReturnCodes.ACCEPTED, ReturnCodes.CREATED ]) if not status: cts_error("CTS can not create Target Endpoint") self.set_status_failed() return ValidationStatus.FAILED self.target_endpoint = headers['location'] cts_message("Target Endpoint created at {location}".format( location=self.target_endpoint)) try: verify = self.__verify_created_target_endpoint( self.target_endpoint, post_target_endpoint_payload) except: verify = False if not verify: cts_error("Service create target endpoint with wrong value") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 4: def _test_create_zone_with_only_one_endpoint(self): print "TEST_CASE::Create zone with only one endpoint" try: initiator_endpoint_link = self.__get_odata_id_links( self.initiator_endpoint) except: cts_error("No valid initiator endpoint for zone creation") self.set_status_blocked() return ValidationStatus.BLOCKED post_create_zone_payload = dict(Links=dict( Endpoints=[{ "@odata.id": initiator_endpoint_link }])) status, status_code, _, headers = self.api_caller.post_resource( self.chosen_zone, self.discovery_container, payload=post_create_zone_payload, acceptable_return_codes=[ ReturnCodes.ACCEPTED, ReturnCodes.CREATED, ReturnCodes.OK ]) if not status: cts_error("CTS can not create Zone") self.set_status_failed() return ValidationStatus.FAILED self.zone_endpoint = headers['location'] cts_message( "Zone created at {location}".format(location=self.zone_endpoint)) if not self.__verify_created_zone(self.zone_endpoint, post_create_zone_payload): cts_error("Service zone created with wrong value") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 5: def _test_patch_zone_with_target_endpoint(self): print "TEST_CASE::Patch zone with target endpoint" initiator_or_target_missing = False try: initiator_endpoint_link = self.__get_odata_id_links( self.initiator_endpoint) except: cts_error("No valid initiator endpoint for zone patching") initiator_or_target_missing = True try: target_endpoint_link = self.__get_odata_id_links( self.target_endpoint) except: cts_error("No valid target endpoint for zone patching") initiator_or_target_missing = True if initiator_or_target_missing: cts_error("Missing resources for zone patching") self.set_status_blocked() return ValidationStatus.BLOCKED patch_create_zone_payload = dict(Links=dict( Endpoints=[{ "@odata.id": initiator_endpoint_link }, { "@odata.id": target_endpoint_link }])) status, status_code, _, headers = self.api_caller.patch_resource( self.zone_endpoint, self.discovery_container, payload=patch_create_zone_payload, acceptable_return_codes=[ReturnCodes.ACCEPTED, ReturnCodes.OK]) if not status: cts_error("CTS can not PATCH Zone") self.set_status_failed() return ValidationStatus.FAILED cts_message( "Zone PATCHED at {location}".format(location=self.zone_endpoint)) if not self.__verify_patched_zone(self.zone_endpoint, patch_create_zone_payload): cts_error("Service zone patched with wrong value") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED # Test case 6: def _test_delete_all_created_endpoints_and_zones(self): status = ValidationStatus.PASSED for resource, link in OrderedDict([ ("volume", self.created_volume), ("zone", self.zone_endpoint), ("initiator endpoint", self.initiator_endpoint), ("target endpoint", self.target_endpoint) ]).iteritems(): print "TEST_CASE::Delete {resource}".format(resource=resource) if link: if not self.__delete_location(link): self.set_status_failed() status = ValidationStatus.join_statuses( status, ValidationStatus.FAILED) else: self.set_status_passed() status = ValidationStatus.join_statuses( status, ValidationStatus.PASSED) else: self.set_status_blocked() status = ValidationStatus.join_statuses( status, ValidationStatus.BLOCKED) return status def __check_resources_have_same_parent_url(self, res1, res2): return (self.discovery_container.get(res1)).parent_url == ( self.discovery_container.get(res2)).parent_url def __choose_endpoint_and_zone_from_fabric_collection( self, endpoint_collection, zone_collection): for ec in endpoint_collection: for zc in zone_collection: if self.__check_resources_have_same_parent_url(ec, zc): return ec, zc return None, None def __find_and_choose_volume(self): for volume in self.__find_volumes_without_any_links(): self.chosen_volume = volume break def __delete_location(self, location_url): status, _, _, _ = self.api_caller.delete_resource( location_url, self.discovery_container, acceptable_return_codes=[ReturnCodes.NO_CONTENT]) if not status: cts_error("Resource at %s was not properly deleted" % location_url) return False return self.__verify_resource_non_exist(location_url) def __verify_resource_non_exist(self, location_url_verification): status, _, _, _, _ = self.api_caller.get_resource( location_url_verification, self.discovery_container, acceptable_return_codes=[ ReturnCodes.BAD_REQUEST, ReturnCodes.NOT_FOUND ]) if not status: cts_error("Resource at %s was not properly deleted" % location_url_verification) return False cts_message("Resource at %s was properly deleted" % location_url_verification) return True def __find_and_choose_endpoint_collection(self): endpoint_collection = dict( self.discovery_container.get_resources( MetadataConstants.ENDPOINT_COLLECTION)) zones_collection = dict( self.discovery_container.get_resources( MetadataConstants.ZONE_COLLECTION)) if not endpoint_collection: cts_error("No Endpoint Collection") self.set_status_blocked() if not zones_collection: cts_error("No Zone Collection") self.set_status_blocked() self.chosen_endpoint, self.chosen_zone = \ self.__choose_endpoint_and_zone_from_fabric_collection( endpoint_collection, zones_collection) def __find_volumes_without_any_links(self, optional_pre_check=False): reusable_volumes = [] logical_drive_collections = dict( self.discovery_container.get_resources( MetadataConstants.VOLUME_COLLECTION, any_child_version=True)) # Allocated Volumes are connected with this same type as Volume volumes_collection_wo_allocated_volumes = [ x for x in logical_drive_collections if not "AllocatedVolumes" in x ] for collection in volumes_collection_wo_allocated_volumes: if not self.chosen_volume_collection: self.chosen_volume_collection = collection logical_drive_groups = dict( self.discovery_container.get_resources( MetadataConstants.VOLUME, any_child_version=True)) volumes_collection = [] for c in logical_drive_groups.keys(): try: # this Volume we can use to our tests if len( self.discovery_container.get(c).body["Links"] ["Oem"]["Intel_RackScale"]["Endpoints"]) == 0: volumes_collection.append(c) except KeyError: pass # if there are any free Volume, suspend test if len(volumes_collection) == 0: if optional_pre_check: return False cts_error( "No reusable Volume. Create a Volume without linked Endpoints" ) self.set_status_blocked() else: reusable_volumes.extend(volumes_collection) return reusable_volumes def __get_odata_id_links(self, link): return (self.discovery_container.get(link)).odata_id # verification code def __verify_patched_zone(self, resource_location, resource_payload): print "MESSAGE::Comparing newly patched zone to given payload" return self.__verify_zone(resource_location, resource_payload) def __verify_created_zone(self, resource_location, resource_payload): print "MESSAGE::Comparing newly created zone to given payload" return self.__verify_zone(resource_location, resource_payload) def __verify_zone(self, resource_location, resource_payload): location_payload_to_compare = JsonComparator.odict_to_dict( self.discovery_container.get(resource_location).body["Links"] ["Endpoints"]) payload_to_compare = resource_payload["Links"]["Endpoints"] return JsonComparator.compare_lists(location_payload_to_compare, payload_to_compare) def __verify_created_target_endpoint(self, resource_location, resource_payload): print "MESSAGE::Comparing newly created target endpoint to given payload" location_payload_to_compare = JsonComparator.odict_to_dict( self.discovery_container.get(resource_location).body) payload_to_compare = resource_payload verification_dict = { 'ConnectedEntities': ['EntityLink', 'EntityRole'], 'Identifiers': ['DurableNameFormat', 'DurableName'] } return self.__verify_body(location_payload_to_compare, payload_to_compare, verification_dict) def __verify_created_initiator_endpoint(self, resource_location, resource_payload): print "MESSAGE::Comparing newly created initiator endpoint to given payload" location_payload_to_compare = JsonComparator.odict_to_dict( self.discovery_container.get(resource_location).body) payload_to_compare = resource_payload verification_dict = { 'ConnectedEntities': ['EntityRole'], 'Identifiers': ['DurableNameFormat', 'DurableName'] } return self.__verify_body(location_payload_to_compare, payload_to_compare, verification_dict) def _verify_size_of_created_volume(self, resource_location, resource_payload): print "MESSAGE::Comparing newly created volume to given payload" discovery_service_capacity = self.discovery_container.get( resource_location).body["CapacityBytes"] resource_payload_capacity = resource_payload["CapacityBytes"] return self.__verify_bytes(resource_payload_capacity, discovery_service_capacity) @staticmethod def __verify_body(resource_1, resource_2, verification_dict): try: for verification_key in verification_dict.keys(): for verification_elem in verification_dict[verification_key]: if resource_1[verification_key][0][verification_elem] != \ resource_2[verification_key][0][verification_elem]: return False except KeyError: return False return True @staticmethod def __verify_bytes(resource_on_payload, resource_discovered, min_bytes=None, max_bytes=None): if not min_bytes: # default 4 mb min_bytes = 4000000 if not max_bytes: # default 4 mb max_bytes = 4000000 if (resource_on_payload - min_bytes) <= resource_discovered <= ( resource_on_payload + max_bytes): return True return False @staticmethod def __verify_strings(resource_on_payload, resource_discovered): resource_on_payload = str(resource_on_payload).lower() resource_discovered = str(resource_discovered).lower() return resource_on_payload == resource_discovered
def test_password_given_user_not(self): configuration = Configuration(**dict(Password=PASSWORD, ApiEndpoint=API_ENDPOINT)) params, kwargs = ApiCaller(configuration)._build_request(RESOURCE) self.assertNotIn("auth", kwargs)
class MetadataManager: METADATA_URL = "/redfish/v1/$metadata" SERVICE_TO_DIR = { ServiceTypes.PODM_1_2: "1.2/PODM", ServiceTypes.PSME_1_2: "1.2/PSME", ServiceTypes.RMM_1_2: "1.2/RMM", ServiceTypes.SS_1_2: "1.2/SS", ServiceTypes.PODM_2_1: "2.1.3/PODM", ServiceTypes.PSME_2_1: "2.1.3/PSME", ServiceTypes.RMM_2_1: "2.1.3/RMM", ServiceTypes.SS_2_1: "2.1.3/SS", ServiceTypes.PODM_2_1_2: "2.1.2/PODM", ServiceTypes.PSME_2_1_2: "2.1.2/PSME", ServiceTypes.RMM_2_1_2: "2.1.2/RMM", ServiceTypes.SS_2_1_2: "2.1.2/SS", ServiceTypes.PODM_2_1_3: "2.1.3/PODM", ServiceTypes.PSME_2_1_3: "2.1.3/PSME", ServiceTypes.RMM_2_1_3: "2.1.3/RMM", ServiceTypes.SS_2_1_3: "2.1.3/SS", ServiceTypes.PODM_2_1_4: "2.1.4/PODM", ServiceTypes.PSME_2_1_4: "2.1.4/PSME", ServiceTypes.RMM_2_1_4: "2.1.4/RMM", ServiceTypes.SS_2_1_4: "2.1.4/SS", ServiceTypes.PODM_2_2: "2.2/PODM", ServiceTypes.PSME_2_2: "2.2/PSME", ServiceTypes.RMM_2_2: "2.2/RMM", ServiceTypes.SS_2_2: "2.2/SS", ServiceTypes.PODM_2_3: "2.3/PODM", ServiceTypes.PSME_2_3: "2.3/PSME", ServiceTypes.RMM_2_3: "2.3/RMM", ServiceTypes.SS_2_3: "2.3/SS", ServiceTypes.PODM_2_4: "2.4/PODM", ServiceTypes.PSME_2_4: "2.4/PSME", ServiceTypes.RMM_2_4: "2.4/RMM", ServiceTypes.SS_2_4: "2.4/SS", ServiceTypes.SCENARIO_2_4: "2.4/SCENARIO", ServiceTypes.RACKSCALE_2_4: "2.4/RACKSCALE", ServiceTypes.REDFISH_2018_1: "redfish/2018.1", ServiceTypes.REDFISH_2018_2: "redfish/2018.2", ServiceTypes.REDFISH_2018_3: "redfish/2018.3" } def __init__(self, qualifiers, ignore_types=None, map_types=None): self.qualifiers = qualifiers self.ignore_types = ignore_types if ignore_types is not None else set() self.map_types = map_types if map_types is not None else dict() self._metadata_container = MetadataContainer(ignore_types=self.ignore_types, map_types=self.map_types) self.loaded_xmls = set() self.api_caller = None def read_metadata_for_services(self, *services): """ :type services: list[string] """ digest = DirDigest(self._metadata_home(), '.xml', DirDigest.LABEL_METADATA) if not digest.is_valid() and not getenv('CTS_SKIP', None): cts_error("Metadata located in {dir} is corrupted or has been tampered. Expected: {expected}, " "Is: {current}", dir=self._metadata_home(), expected=digest.official_digest, current=digest.digest) cts_message("{count} xml files have been found in {dir}".format(count=len(digest), dir=self._metadata_home())) digest.report_differences() for service in services: try: self.metadata_dir = os.path.join(self._metadata_home(), MetadataManager.SERVICE_TO_DIR[service]) self.read_metadata_from_dir(self.metadata_dir) except KeyError: cts_error("Internal error. Unknown metadata for service {service}", service=service) return False return True def read_metadata_from_dir(self, dir, root_file=None): if root_file is None: root_file = ROOT_XML_FILE if os.path.isdir(dir): self.metadata_dir = dir self._process_schema_file(root_file) return True else: cts_error("Internal error. Metadata directory {metadata_dir} not found", metadata_dir=dir) return False def _validate_xml_file(self, file_uri, metadata_text): try: doc = etree.fromstring(metadata_text.lstrip()) except etree.XMLSyntaxError as err: cts_error('XML Syntax Error in file {file}', file=file_uri) raise MetadataMalformed() def read_metadata_from_strings(self, file_uri, *metadata): """ :type file_uri: string :type metadata: list[string] :rtype: cts_core.metadata.metadata_container.MetadataContainer """ for metadata_text in metadata: self._validate_xml_file(file_uri, metadata_text) metadata_soup = Commons.text_to_soup(metadata_text) for reference in metadata_soup.find_all(REFERENCE): self._process_reference(reference) for schema in metadata_soup.find_all(SCHEMA): self._process_namespace(schema) return self.metadata_container def download_metadata(self, configuration): self.api_caller = ApiCaller(configuration) self._process_schema_file(MetadataManager.METADATA_URL) return True @staticmethod def _metadata_home(): return Constants.METADATA_HOME_DIR def _process_reference(self, reference_soup): try: self._process_schema_file(reference_soup[URI]) except KeyError as uri: cts_error("Incorrect reference. URI not found") @property def metadata_container(self): return self._metadata_container def _process_schema_file(self, file_uri): if file_uri in self.loaded_xmls: return self.loaded_xmls.add(file_uri) if self.api_caller is None: self._process_local_schema_file(file_uri) else: self._process_remote_schema_file(file_uri) def _process_local_schema_file(self, file_uri): try: full_uri = os.path.join(self.metadata_dir, file_uri) with open(full_uri) as f: self.read_metadata_from_strings(file_uri, f.read()) except IOError: cts_error("Metadata file {file} not found", file=full_uri) def _process_remote_schema_file(self, file_uri): _, status, _, response_body, _ = self.api_caller.get_xml(file_uri) if not status: cts_error("Error while accessing metadata {uri}", uri=file_uri) else: self.read_metadata_from_strings(file_uri, response_body) def _process_namespace(self, schema): """ :type schema: bs4.element.Tag """ try: namespace_name = schema[NAMESPACE] except KeyError: cts_warning( "Incorrect schema definition {schema_definition},\n missing of namespace name", schema_definition=str(schema)) for entity_soup in schema.find_all(ENTITY_TYPE): entity = Entity(self._metadata_container, namespace_name, entity_soup, self.qualifiers) self._metadata_container.entities[entity.name] = entity for type_soup in schema.find_all(ENUM_TYPE): enum_type = EnumType(self._metadata_container, namespace_name, type_soup, self.qualifiers) self._metadata_container.types[enum_type.name] = enum_type for type_soup in schema.find_all(COMPLEX_TYPE): complex_type = ComplexType(self._metadata_container, namespace_name, type_soup, self.qualifiers) self._metadata_container.types[complex_type.name] = complex_type for type_soup in schema.find_all(TYPE_DEFINITION): type_definition = TypeDefinition(self._metadata_container, namespace_name, type_soup, self.qualifiers) self._metadata_container.types[type_definition.name] = type_definition
class CRUDOperations(CtsTestScript): TIMEOUT = 600 DESCRIPTION = """test script validating CRUD (create, read, update and delete) functionalities""" def run(self): if self.metadata_container is None: return print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover(MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) self.api_caller = ApiCaller(self.configuration) self.crud_vlan() def _test_case_create_vlan(self, vlan_network_interface_collection): print "TEST_CASE::Create VLAN" status, status_code, response_body, headers = \ self.api_caller.post_resource( vlan_network_interface_collection, self.discovery_container, payload=self.post_vlan_payload) if not status: cts_error("Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False try: self.created_vlan_network_interface = headers["Location"] self.created_vlan_network_interface_netlock = self.discovery_container.get_netloc( vlan_network_interface_collection) print "MESSAGE::Newly created VLAN Network Interface URI: %s" % self.created_vlan_network_interface except KeyError: cts_error("Incorrect response - header shall contain the location of the created resource") self.set_status_failed() return False print "MESSAGE::VLAN Network Interface created" self.set_status_passed() return True def _test_case_get_created_vlan(self): print "TEST_CASE::Get created VLAN" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_vlan_network_interface, self.discovery_container, api_endpoint_override=self.created_vlan_network_interface_netlock) if not status: cts_error("Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False else: print "MESSAGE::Comparing newly created VLAN Network Interface to given payload" # PSME is unable to set "Name" and "VLANEnable" fields properly but only "VLANEnable" is mandatory if not JsonComparator.compare_json_to_golden(response_body, self.post_vlan_payload, ignore=["/VLANEnable"]): cts_error("Newly created VLAN Network Interface's body incorrect") self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface correct" self.set_status_passed() return True def _test_case_delete_created_vlan(self): print "TEST_CASE::Delete created VLAN" status, status_code, response_body, _ = self.api_caller.delete_resource(self.created_vlan_network_interface, self.discovery_container) if not status: cts_error("Wrong status code of DELETE response: {status_code};" " response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface deleted" self.set_status_passed() return True def _test_case_get_deleted_vlan(self): print "TEST_CASE::Get deleted VLAN" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_vlan_network_interface, self.discovery_container, acceptable_return_codes=[ReturnCodes.NOT_FOUND], api_endpoint_override=self.created_vlan_network_interface_netlock) if not status: cts_error("Wrong status code of GET response after deletion: {status_code};" " response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface not found (as intended)" self.set_status_passed() return True def crud_vlan(self): """ Test is trying to perform CRUD (create, read, update, delete) operations on a VLAN Network Interface resource """ requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.VLAN_NETWORK_INTERFACE_COLLECTION, min=1), ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) if status is ValidationStatus.PASSED_WITH_WARNINGS: cts_warning("Without some components it is not possible to perform tests.") print "STATUS::{status}".format(status=status) return ValidationStatus.PASSED_WITH_WARNINGS if status is ValidationStatus.FAILED: return ValidationStatus.BLOCKED vlan_network_interface_collections = \ dict(self.discovery_container.get_resources(MetadataConstants.VLAN_NETWORK_INTERFACE_COLLECTION, any_child_version=True)) chosen_vlan_network_interface_collection = None new_vlan_id = 0 for vlan_network_interface_collection in vlan_network_interface_collections: try: parent_port = get_parent_odata_id(vlan_network_interface_collection) parent_port_id = self.discovery_container[parent_port].body["PortId"] except: continue # For arista setup, only ever try adding VLANs on the main /1 port if parent_port_id.endswith("/2") or parent_port_id.endswith("/3") or parent_port_id.endswith("/4"): continue present_vlan_ids = [] vlan_network_interfaces = \ dict(self.discovery_container. get_resources(MetadataConstants.VLAN_NETWORK_INTERFACE, constraints=[from_collection(vlan_network_interface_collection)], any_child_version=True)) if len(vlan_network_interfaces) > MAX_VLAN_ID - MIN_VLAN_ID: print "MESSAGE::No free VLAN id available on collection {}".format(vlan_network_interface_collection) continue for resource_link, resource in vlan_network_interfaces.iteritems(): try: present_vlan_ids.append(resource.body["VLANId"]) except: cts_error("Inorrect resource {resource_link:id} structure", **locals()) for possible_id in range(MIN_VLAN_ID, MAX_VLAN_ID + 1): if possible_id not in present_vlan_ids: chosen_vlan_network_interface_collection = vlan_network_interface_collection new_vlan_id = possible_id break if chosen_vlan_network_interface_collection: break if chosen_vlan_network_interface_collection: print "MESSAGE::CRUD test will be performed on vlan network interface collection %s" % \ chosen_vlan_network_interface_collection else: cts_warning('No free VLAN id available on any of the suitable ports (perhaps there are no suitable ports)') return ValidationStatus.BLOCKED self.post_vlan_payload = dict(VLANId=new_vlan_id, VLANEnable=True, Oem=dict( Intel_RackScale=dict( Tagged=True ))) if self._test_case_create_vlan(chosen_vlan_network_interface_collection): self._test_case_get_created_vlan() self._test_case_delete_created_vlan() self._test_case_get_deleted_vlan()
class ValidateUseCase(CtsTestScript): TIMEOUT = 600 DESCRIPTION = """test case validating api responses against provided use case""" CONFIGURATION_PARAMETERS = [ TestScript.ConfigurationParameter( parameter_name="UseCase", parameter_description="Use Case name", parameter_type=str, is_required=False, parameter_default_value=[]) ] def run(self): json_loader = JsonLoader() if self.metadata_container is None: return if not self.configuration.UseCase: json_loader.print_available_actions() return actions_to_take = json_loader.get_use_case(self.configuration.UseCase) specific_action = json_loader.get_use_case_action( actions_to_take['filepath']) print('TEST_CASE::API crawling') api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover( MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) configuration = self.configuration self.api_caller = ApiCaller(configuration) print('TEST_CASE::Action Runner') status = self.action_runner(specific_action) print('STATUS::{status}'.format(status=status)) def action_runner(self, specific_action): actions_status = ValidationStatus.RUNNING for action_step, action in enumerate(specific_action['actions'], 1): step_counter = "%d/%d" % (action_step, len(specific_action['actions'])) action_type = action['type'] if action_type == 'timeout': try: self.perform_timeout(step_counter, action['value']) except IndexError: self.perform_timeout(step_counter) continue retry_if_fail, retry_timeout = self.__get_retry_information(action) action_args = (specific_action['name'], action['name'], action['action'], step_counter, int(retry_if_fail), int(retry_timeout)) if action_type == 'external': actions_status = ValidationStatus.join_statuses( self.perform_external_command(*action_args)) continue elif action_type == 'usecase': actions_status = ValidationStatus.join_statuses( self.perform_action(*action_args)) return actions_status @staticmethod def perform_timeout(step_counter, timeout=1): cts_message("Step: %s" % step_counter) cts_message("Timeout: %s" % timeout) sleep(float(timeout)) @CaseInformation def perform_external_command(self, test_name, case_name, action, step_counter, retry=0, timeout=0): """ Perform command on external host :param test_name: !! used by decorator :param case_name: !! used by decorator :param action: :param step_counter: !! used by decorator :param retry: :param timeout: :return: """ # CONNECTION PHASE response_from_remote_host = self._connection_phase(action) # VERIFICATION PHASE print(response_from_remote_host) verification_status = self._verification_phase( action, raw_response=response_from_remote_host) if not verification_status: return ValidationStatus.FAILED return ValidationStatus.PASSED @staticmethod def run_commands_on_host(commands, credentials): responses_from_ssh_target = [] with ExternalCommandOverSSH(**credentials) as host: for cmd in commands: _, stdout, stderr = host.exec_command(cmd) err = stderr.read() if err is not '': cts_error(err) responses_from_ssh_target.append(stdout.read()) return responses_from_ssh_target[-1] def _connection_phase(self, action): """ Do actions on remote and return last response """ tgt_credential = action['target_credential'] command = action['command'] first_target = { "target_hostname": tgt_credential['hostname'], "target_username": tgt_credential['username'], "target_password": tgt_credential['password'], "target_port": tgt_credential['port'] } try: second_tgt_credential = action['jump_target_credential'] second_target = { "second_hostname": second_tgt_credential['hostname'], "second_username": second_tgt_credential['username'], "second_password": second_tgt_credential['password'], "second_port": second_tgt_credential['port'] } except KeyError: return self.run_commands_on_host(command, first_target) return self.run_commands_on_host( command, self.__merge_dicts(first_target, second_target)) def _verification_phase(self, action, raw_response=None): external_command = action['response']['command'] expected_response = action['response']['result'] command_type, command_location = self.__verify_command_type( external_command) command_args = self.__verify_command_args(external_command) external_action = ExecuteExternalScript( script_path=command_location, raw_response=raw_response, expected_result=expected_response) if not external_action(**command_args): cts_error('Script response IS NOT equal to excepted.') return False cts_message('Script response is equal to excepted.') return True @CaseInformation def perform_action(self, test_name, case_name, action, step_counter, retry=0, timeout=0): case_action = CaseAction(**action) cts_message("_____") cts_message("{name}: {step}\n\t\t {description}".format( name=test_name, step=step_counter, description=case_name)) cts_message("{request_method}: {request}".format( request_method=action['request_method'], request=action['request'].replace("{", "(").replace("}", ")"))) resource_type, resource_id, resource_action = self._get_target_information( case_action.request) discovered_systems = self.discovery_container.get_resources( resource_type, any_child_version=True) if not discovered_systems: cts_error('Cannot find %s' % resource_type) return ValidationStatus.BLOCKED try: computer_odata_id = self.discovery_container.get( discovered_systems[resource_id][0]).odata_id except IndexError: cts_error('Cannot find %s' % resource_type) return ValidationStatus.BLOCKED action_endpoint = '{odata_id}/{action_name}'.format( odata_id=computer_odata_id, action_name='/'.join(resource_action)) status, _, _, _ = self.api_caller.post_resource( action_endpoint, self.discovery_container, case_action.payload, acceptable_return_codes=case_action.response) if (not status) and (retry > 0): cts_message("Operation failed, left retry %s" % retry) cts_message("Timeout: %s" % timeout) sleep(float(timeout)) self.perform_action(test_name, case_name, action, step_counter, retry=(retry - 1), timeout=timeout) if not status: cts_error('Cannot execute action %s' % action_endpoint) return ValidationStatus.FAILED cts_message('End of: %s' % case_name) return ValidationStatus.PASSED def _get_target_information(self, request): request_cleaned = request.replace('{', '').replace('}', '').split('/') resource_type = request_cleaned[0] resource_id = self._choose_resource(request_cleaned[1]) action = request_cleaned[2:] return resource_type, resource_id, action @staticmethod def _choose_resource(number): return 0 if number == 'any' else int(number) @staticmethod def __verify_command_type(external_cmd): command_type, command_location = (None, ) * 2 try: command_type = external_cmd[0] if command_type.lower() != 'python': cts_message('CTS support only Python external scripts') return False command_location = external_cmd[1] except IndexError: cts_error('Missing verification command details') return command_type, command_location def __verify_command_args(self, external_cmd): command_args = None try: command_args = self.__generate_dict_from_custom(external_cmd[2:]) except IndexError: pass return command_args @staticmethod def __get_retry_information(action): """ :param action: :return: retry_if_fail, retry_timeout """ retry_if_fail, retry_timeout = 0, 1 try: retry_if_fail, retry_timeout = action['action']['retry_if_fail'], \ action['action']['retry_timeout'] except KeyError: pass return retry_if_fail, retry_timeout # HELPERS @staticmethod def __generate_dict_from_custom(list_values): dict_from_list = {} for element in list_values: k, v = element.split(":") dict_from_list[k] = v return dict_from_list @staticmethod def __merge_dicts(a, b): c = a.copy() c.update(b) return c
def node_reset_support(self, discovery_container): """ Test is checking if client is able to perform compute blade reset :type discovery_container: cts_core.discovery.discovery_container.DiscoveryContainer :type self.metadata_container: cts_core.metadata.self.metadata_container.MetadataContainer """ print "TEST_CASE::Node reset support" test_timeout = 30 computer_systems = {} api_caller = ApiCaller(self.configuration) for resource_link, resource in discovery_container.iteritems(): if self.metadata_container.entities[ MetadataConstants.COMPUTER_SYSTEM_1_0_0].is_compatible( resource.odata_type): print "MESSAGE::Found Computer System in %s" % resource_link computer_systems[resource_link] = resource tested_computer_system, tested_computer_system_state = None, None for system in computer_systems.keys(): try: status = computer_systems[system].body["Status"] memory_gib = computer_systems[system].body["MemorySummary"][ "TotalSystemMemoryGiB"] if status["Health"] == "OK" and status[ "State"] == "Enabled" and memory_gib is not None: tested_computer_system = computer_systems[system] tested_computer_system_state = computer_systems[ system].body["PowerState"] break except: continue if not tested_computer_system: cts_error( "No Computer System suitable for test found (must be enabled and healthy)" ) self.set_validation_status(ValidationStatus.BLOCKED) return # mapping accepted states with commands to send computer_system_reset_commands = {"Off": "On", "On": "ForceOff"} if tested_computer_system_state not in computer_system_reset_commands.keys( ): cts_error("Computer System's state neither \"Off\" nor \"On\"") self.set_validation_status(ValidationStatus.FAILED) return else: # by substracting Current System state from {"On", "Off"} set we get the state we expect the Computer System # to be in after Reset command expected_computer_system_state = list( set(computer_system_reset_commands.keys()) - {tested_computer_system_state})[0] print "MESSAGE::Will try to Power %s Computer System %s " % ( expected_computer_system_state, tested_computer_system.odata_id) status, status_code, response_body, _ = \ api_caller.post_resource("%s/Actions/ComputerSystem.Reset" % tested_computer_system.url, discovery_container, payload={ "ResetType": "%s" % computer_system_reset_commands[ tested_computer_system_state]}, acceptable_return_codes=[ ReturnCodes.NO_CONTENT], api_endpoint_override=tested_computer_system.netloc) if status: print "MESSAGE::Reset action accepted by server" else: cts_error( "Incorrect status code {status_code}, response body: {response_body:response_body}", **locals()) self.set_validation_status(ValidationStatus.FAILED) return print( "MESSAGE::Waiting up to %ss for the Computer System to change its state to %s" % (test_timeout, expected_computer_system_state)) test_duration = 0 while test_duration < test_timeout: sleep(1) test_duration += 1 link, status, status_code, response_body, _ = \ api_caller.get_resource(tested_computer_system.url, discovery_container, api_endpoint_override=tested_computer_system.netloc) if not status: cts_error( "Could not get {tested_computer_system} info from the server; status code: {status_code}; " + "response body {response_body:response_body}", **locals()) self.set_validation_status(ValidationStatus.FAILED) return if response_body["PowerState"] == expected_computer_system_state: print "Message::Computer System has successfully change its state" self.set_validation_status(ValidationStatus.PASSED) return cts_error("Computer System did not change its state in time") self.set_validation_status(ValidationStatus.FAILED) return
class CRUDOperations(CtsTestScript): TIMEOUT = 600 DESCRIPTION = """test script validating CRUD (create, read, update and delete) functionalities""" def run(self): test_name = "Storage Services CRUD test" print "MESSAGE::%s starting" % test_name print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) self.discovery_container, status = api_explorer.discover(MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1), Requirement(MetadataConstants.STORAGE_SERVICE, min=1), Requirement(MetadataConstants.LOGICAL_DRIVE, min=2) ] preconditions = Preconditions(self.metadata_container, requirements) status = preconditions.validate(self.discovery_container) self.api_caller = ApiCaller(self.configuration) status = ValidationStatus.join_statuses(status, self.crud_logical_drive()) status = ValidationStatus.join_statuses(status, self.crud_remote_target()) print "MESSAGE::%s overall status: %s" % (test_name, ColorPrinter.format_status(status)) def _test_case_create_logical_drive(self, logical_drive_collection, payload=None): print "TEST_CASE::Create a logical drive" if not payload: payload = self.post_logical_drive_payload status, status_code, response_body, headers = self.api_caller.post_resource( logical_drive_collection, payload=payload) if not status: print "ERROR::Wrong status code of POST response: %d" % status_code self.set_status_failed() return ValidationStatus.FAILED try: self.created_logical_drive = headers["Location"] print "MESSAGE::Newly created logical drive url %s" % self.created_logical_drive except KeyError: print "ERROR::In header shall be provided location to created resource" self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Logical drive created" self.set_status_passed() return ValidationStatus.PASSED def _test_case_get_created_logical_drive(self, logical_drive=None): print "TEST_CASE::Get the created logical drive" if not logical_drive: logical_drive = self.created_logical_drive status, status_code, response_body, _ = self.api_caller.get_resource(logical_drive) if not status: print "ERROR::Wrong status code of GET response: %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return ValidationStatus.FAILED else: api_resource = ApiResource(response_body, response_body["@odata.type"]) self.discovery_container.add_resource(logical_drive, api_resource) print "MESSAGE::Comparing newly created logical drive to given payload" ignored_fields = ["/Name", "/Image"] # Name setting not supported by PSME REST API, Image is optional print "MESSAGE::Comparing newly created logical drive to given payload" if not JsonComparator.compare_json_to_golden(response_body, self.expected_created_logical_drive_body, ignore = ignored_fields): print "ERROR::Newly created logical drive's body incorrect" self.set_status_failed() print "MESSAGE::Proceeding with deleting the resource" return ValidationStatus.FAILED print "MESSAGE::Newly created logical drive's body correct" self.set_status_passed() return ValidationStatus.PASSED def _test_case_update_created_logical_drive(self): print "TEST_CASE::Update the created logical drive" patch_logical_drive_payload = dict( Bootable = not self.post_logical_drive_payload["Bootable"] ) status, status_code, response_body, _ = self.api_caller.patch_resource(self.created_logical_drive, payload=patch_logical_drive_payload) if not status: print "ERROR::Wrong status code of PATCH reponse %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return ValidationStatus.FAILED status, status_code, response_body, _ = self.api_caller.get_resource(self.created_logical_drive) if not status: print "ERROR::Wrong status code of GET reponse %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return ValidationStatus.FAILED else: print "MESSAGE::Verifying whether the logical drive's update has taken place" if not JsonComparator.compare_json_to_golden(response_body, patch_logical_drive_payload): print "ERROR::Newly created logical drive could not be updated" self.set_status_failed() print "MESSAGE::Proceeding with deleting the resource" return ValidationStatus.FAILED print "MESSAGE::Newly created logical drive updated correctly" self.set_status_passed() return ValidationStatus.PASSED def _test_case_delete_created_logical_drive(self, logical_drive=None): print "TEST_CASE::Delete the created logical drive" if not logical_drive: logical_drive = self.created_logical_drive status, status_code, response_body, _ = self._delete_resource(logical_drive) if not status: self.set_status_failed() return ValidationStatus.FAILED self.discovery_container.remove_resource(logical_drive) print "MESSAGE::Newly created logical drive deleted" self.set_status_passed() return ValidationStatus.PASSED def _test_case_get_deleted_logical_drive(self, logical_drive=None): print "TEST_CASE::Get the deleted logical drive" if not logical_drive: logical_drive = self.created_logical_drive status, status_code, response_body, _ = self.api_caller.get_resource(logical_drive, acceptable_return_codes = [ReturnCodes.NOT_FOUND]) if not status: print "ERROR::Wrong status code of GET response after deletion: %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Newly created logical drive not found (as intended)" self.set_status_passed() return ValidationStatus.PASSED def crud_logical_drive(self, called_from_crud_remote_target=None): """ Test is trying to perform CRUD (create, read, update, delete) operations on a logical volume :type discovery_container: cts_core.discovery.discovery_container.DiscoveryContainer :type self.metadata_container: cts_core.metadata.self.metadata_container.MetadataContainer """ status = ValidationStatus.BLOCKED lvm_found = False logical_volumes = dict() logical_volume_groups = dict() logical_drive_collections = dict() for resource_link, resource in self.discovery_container.iteritems(): ok = True if self.metadata_container.entities[MetadataConstants.LOGICAL_DRIVE].compare_entities(resource.odata_type): print "DEBUG::found entity of type %s in %s" % (MetadataConstants.LOGICAL_DRIVE, resource_link) try: if resource.body["Type"] == "LVM": lvm_found = True if resource.body["Mode"] == "LVG": logical_volume_groups[resource_link] = resource elif resource.body["Mode"] == "LV": if "CapacityGiB" not in resource.body.keys(): print "DEBUG::%s drive does not have specified capacity" % resource_link ok = False if "Snapshot" in resource.body.keys() and resource.body["Snapshot"] == True: print "DEBUG::%s cannot be used - drive is a snapshot volume" % resource_link ok = False if ok: logical_volumes[resource_link] = resource elif resource.body["Type"] == "CEPH": if resource.body["Mode"] == "Pool": logical_volume_groups[resource_link] = resource elif resource.body["Mode"] == "RBD": if "CapacityGiB" not in resource.body.keys(): print "DEBUG::%s drive does not have specified capacity" % resource_link ok = False if ok: logical_volumes[resource_link] = resource except: print "WARNING::Incorrect resource %s structure" % resource_link elif self.metadata_container.entities[MetadataConstants.LOGICAL_DRIVE_COLLECTION]\ .compare_entities(resource.odata_type): logical_drive_collections[resource_link] = resource if lvm_found and (not logical_volume_groups or not logical_volumes): print "ERROR::Insufficient resources available on API for test. Found %s logical volumes and %s logical " \ " volume groups" % (len(logical_volumes), len(logical_volume_groups)) return ValidationStatus.BLOCKED # to speed things up, always look for the lowest capacity logical volume lowest_capacity = float('inf') chosen_logical_drive_collection, chosen_logical_volume, chosen_logical_volume_group = None, None, None inherited_bootable = None if not logical_volume_groups: if lvm_found: print "ERROR::No LVM volume group found, aborting" else: print "ERROR::No CEPH Pool device found, aborting" return ValidationStatus.BLOCKED for lvg_link, lvg in logical_volume_groups.iteritems(): # finding a collection matching this volume group possible_collections = [collection for collection in logical_drive_collections.keys() if collection in lvg_link] if not possible_collections: continue try: for lv in lvg.body["Links"]["LogicalDrives"]: # iterating LVs that have this LVG as a parent try: lv_link = lv["@odata.id"] lv_type = logical_volumes[lv_link].body["Type"] lv_mode = logical_volumes[lv_link].body["Mode"] lv_capacity = logical_volumes[lv_link].body["CapacityGiB"] lv_bootable = logical_volumes[lv_link].body["Bootable"] if lv_capacity < lowest_capacity: lowest_capacity = lv_capacity inherited_bootable = lv_bootable chosen_logical_volume = lv_link chosen_logical_volume_type = lv_type chosen_logical_volume_mode = lv_mode chosen_logical_volume_group = lvg_link chosen_logical_drive_collection = possible_collections[0] except KeyError: pass except KeyError: pass self.post_logical_drive_payload = dict( Name="LVM Logical Drive created by CTS", Type=chosen_logical_volume_type, Mode=chosen_logical_volume_mode, Protected=False, CapacityGiB=self.discovery_container[chosen_logical_volume].body["CapacityGiB"], Bootable=inherited_bootable if inherited_bootable else False, Snapshot=True, Links=dict( LogicalDrives=[{"@odata.id": chosen_logical_volume_group}], MasterDrive={"@odata.id": chosen_logical_volume} ) ) # Creating the first RBD if no other was found if not chosen_logical_volume: if lvm_found: print "ERROR::Could not find a (logical volume, logical volume group) pair suitable for the test" return status else: self.post_logical_drive_payload = dict( Name="CEPH Logical Drive created by CTS", Type="CEPH", Mode="RBD", Protected=False, CapacityGiB=self.discovery_container[chosen_logical_volume_group].body["CapacityGiB"]/2, Bootable=False, Snapshot=False, Links=dict( LogicalDrives=[{"@odata.id": chosen_logical_volume_group}], MasterDrive=None ) ) print "MESSAGE::Logical drive CRUD test will be performed on logical drive collection %s, creating logical volume " \ "based on %s on group %s" % (chosen_logical_drive_collection, chosen_logical_volume, chosen_logical_volume_group) self.expected_created_logical_drive_body = deepcopy(self.post_logical_drive_payload) # The logical drive in LogicalDrives link is really the volume group and is going to be seen under # UsedBy on REST self.expected_created_logical_drive_body["Links"]["UsedBy"] = \ self.expected_created_logical_drive_body["Links"]["LogicalDrives"] self.expected_created_logical_drive_body["Links"]["LogicalDrives"] = [] status = self._test_case_create_logical_drive(chosen_logical_drive_collection) if status == ValidationStatus.PASSED: # only perform other tests if creation was successful status = ValidationStatus.join_statuses(status, self._test_case_get_created_logical_drive()) if called_from_crud_remote_target: status = ValidationStatus.join_statuses(status, self.crud_remote_target(called_from_crud_logical_drive=True)) else: status = ValidationStatus.join_statuses(status, self._test_case_update_created_logical_drive()) status = ValidationStatus.join_statuses(status, self._test_case_delete_created_logical_drive()) status = ValidationStatus.join_statuses(status, self._test_case_get_deleted_logical_drive()) return status def _test_case_create_remote_target(self, remote_target_collection): print "TEST_CASE::Create a remote target" status, status_code, response_body, headers = self.api_caller.post_resource( remote_target_collection, payload=self.post_remote_target_payload) if not status: print "ERROR::Wrong status code of POST response: %d" % status_code self.set_status_failed() return ValidationStatus.FAILED try: self.created_remote_target = headers["Location"] print "MESSAGE::Newly created remote target url %s" % self.created_remote_target except KeyError: print "ERROR::In header shall be provided location to created resource" self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Remote target created" self.set_status_passed() return ValidationStatus.PASSED def _test_case_get_created_remote_target(self): print "TEST_CASE::Get the created remote target" status, status_code, response_body, _ = self.api_caller.get_resource(self.created_remote_target) if not status: print "ERROR::Wrong status code of GET response: %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return ValidationStatus.FAILED else: print "MESSAGE::Comparing newly created remote target to given payload" if not JsonComparator.compare_json_to_golden(response_body, self.post_remote_target_payload): print "ERROR::Newly created remote target's body incorrect" self.set_status_failed() print "MESSAGE::Proceeding with deleting the resource" return ValidationStatus.FAILED print "MESSAGE::Newly created remote target's correct" self.set_status_passed() return ValidationStatus.PASSED def _test_case_logical_volume_has_link_to_created_target(self, logical_volume): print "TEST_CASE::Check for a link to the created target in logical volume's body" status, status_code, response_body, _ = self.api_caller.get_resource(logical_volume) if not status: print "ERROR::Wrong status code of GET response: %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return ValidationStatus.FAILED else: try: target = urlparse(self.created_remote_target).path if not {"@odata.id": target} in response_body["Links"]["Targets"]\ and not {"@odata.id": target + "/"} in response_body["Links"]["Targets"]: print "ERROR::No link to the created target in the logical volume's body" self.set_status_failed() return ValidationStatus.FAILED except KeyError: print "ERROR::No Links/Targets array present in the logical volume's body" self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::A link from the logical volume to the target is present" self.set_status_passed() return ValidationStatus.PASSED def _test_case_update_created_remote_target(self): print "TEST_CASE::Update the created remote target" patch_remote_target_payload = dict( Initiator=[dict( iSCSI=dict( InitiatorIQN="cts.initiator:patched_cts_initiator" ) )] ) status, status_code, response_body, _ = self.api_caller.patch_resource(self.created_remote_target, payload=patch_remote_target_payload) if not status: print "ERROR::Wrong status code of PATCH reponse %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return ValidationStatus.FAILED status, status_code, response_body, _ = self.api_caller.get_resource(self.created_remote_target) if not status: print "ERROR::Wrong status code of GET reponse %s" % status_code print "ERROR::Response body:\n%s" % response_body self.set_status_failed() return ValidationStatus.FAILED else: print "MESSAGE::Verifying whether the remote target's update has taken place" if not JsonComparator.compare_json_to_golden(response_body, patch_remote_target_payload): print "ERROR::Newly created remote target could not be updated" self.set_status_failed() print "MESSAGE::Proceeding with deleting the resource" return ValidationStatus.FAILED print "MESSAGE::Newly created remote target updated correctly" self.set_status_passed() return ValidationStatus.PASSED def _test_case_delete_created_remote_target(self): print "TEST_CASE::Delete the created remote target" status, status_code, response_body, _ = self._delete_resource(self.created_remote_target) if not status: self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Newly created remote target deleted" self.set_status_passed() return ValidationStatus.PASSED def _test_case_get_deleted_remote_target(self): print "TEST_CASE::Get the deleted remote target" status, status_code, response_body, _ = self.api_caller.get_resource(self.created_remote_target, acceptable_return_codes = [ReturnCodes.NOT_FOUND]) if not status: print "ERROR::Wrong status code of GET response after deletion: %s" % status_code self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Newly created remote target not found (as intended)" self.set_status_passed() return ValidationStatus.PASSED def _delete_resource(self, resource_link): status, status_code, response_body, headers = self.api_caller.delete_resource(resource_link) if not status: print "ERROR::Wrong status code of DELETE reponse: %s" % status_code print "ERROR::Response body:\n%s" % response_body return status, status_code, response_body, headers def crud_remote_target(self, called_from_crud_logical_drive=None): """ Test is checking for rsa compute blade presence :type discovery_container: cts_core.discovery.discovery_container.DiscoveryContainer :type self.metadata_container: cts_core.metadata.self.metadata_container.MetadataContainer """ target_iqn = "cts.target:cts_test_target" target_iqns = set() logical_volumes = dict() remote_target_collections = dict() for resource_link, resource in self.discovery_container.iteritems(): if self.metadata_container.entities[MetadataConstants.LOGICAL_DRIVE].compare_entities(resource.odata_type): print "DEBUG::found entity of type %s in %s" % (MetadataConstants.LOGICAL_DRIVE, resource_link) try: # looking for unprotected LVs with no targets associated with it if ((resource.body["Mode"] == "LV" or resource.body["Mode"] == "RBD") and not len(resource.body["Links"]["Targets"]) and not resource.body["Protected"]): logical_volumes[resource_link] = resource except: print "WARNING::Incorrect resource %s structure" % resource_link elif self.metadata_container.entities[MetadataConstants.REMOTE_TARGET_COLLECTION].compare_entities(resource.odata_type): remote_target_collections[resource_link] = resource elif self.metadata_container.entities[MetadataConstants.REMOTE_TARGET].compare_entities( resource.odata_type): try: for address in resource.body["Addresses"]: target_iqns.add(address["iSCSI"]["TargetIQN"]) except KeyError: pass if target_iqn in target_iqns: iqn_number = 0 while target_iqn + str(iqn_number) in target_iqns: iqn_number += 1 target_iqn = target_iqn + str(iqn_number) if not logical_volumes: print "WARNING::Insufficient resources available on API for test. Could not find an unprotected logical volume with"\ " no targets attached" if called_from_crud_logical_drive: print "ERROR::Creating a new logical volume for the target failed, skipping remote target tests" return ValidationStatus.BLOCKED else: print "MESSAGE::Trying to create a new logical volume for the target, then proceeding with remote target CRUD test" return self.crud_logical_drive(called_from_crud_remote_target=True) chosen_logical_volume = logical_volumes.keys()[0] chosen_remote_target_collection = remote_target_collections.keys()[0] print "MESSAGE::Remote Target CRUD test will be performed on remote target collection %s, creating remote target " \ "based on %s" % (chosen_remote_target_collection, chosen_logical_volume) self.post_remote_target_payload = dict( Addresses=[dict( iSCSI=dict( TargetIQN=target_iqn, TargetLUN=[dict( LUN=1, LogicalDrive={"@odata.id": chosen_logical_volume} )], ) )], Initiator=[dict( iSCSI=dict( InitiatorIQN="cts.initiator:initiator_cts_test" ) )] ) status = self._test_case_create_remote_target(chosen_remote_target_collection) if status == ValidationStatus.PASSED: # only perform other tests if creation was successful status = ValidationStatus.join_statuses(status, self._test_case_get_created_remote_target()) status = ValidationStatus.join_statuses(status, self._test_case_logical_volume_has_link_to_created_target( chosen_logical_volume)) status = ValidationStatus.join_statuses(status, self._test_case_update_created_remote_target()) status = ValidationStatus.join_statuses(status, self._test_case_delete_created_remote_target()) status = ValidationStatus.join_statuses(status, self._test_case_get_deleted_remote_target()) return status
def download_metadata(self, configuration): self.api_caller = ApiCaller(configuration) self._process_schema_file(MetadataManager.METADATA_URL) return True
def node_reset_support(self, discovery_container): """ Test is checking if client is able to perform compute blade reset :type discovery_container: cts_core.discovery.discovery_container.DiscoveryContainer :type self.metadata_container: cts_core.metadata.self.metadata_container.MetadataContainer """ print "TEST_CASE::Node reset support" test_timeout = 90 computer_systems = {} api_caller = ApiCaller(self.configuration) for resource_link, resource in discovery_container.iteritems(): if self.metadata_container.entities[MetadataConstants.COMPUTER_SYSTEM_1_0_0].is_compatible(resource.odata_type): print "MESSAGE::Found Computer System in %s" % resource_link computer_systems[resource_link] = resource tested_computer_system, tested_computer_system_state = None, None for system in computer_systems.keys(): try: status = computer_systems[system].body["Status"] memory_gib = computer_systems[system].body["MemorySummary"]["TotalSystemMemoryGiB"] if status["Health"] == "OK" and status["State"] == "Enabled" and memory_gib is not None: tested_computer_system = computer_systems[system] tested_computer_system_state = computer_systems[system].body["PowerState"] break except: continue if not tested_computer_system: cts_error("No Computer System suitable for test found (must be enabled and healthy)") self.set_validation_status(ValidationStatus.BLOCKED) return # mapping accepted states with commands to send computer_system_reset_commands = {"Off": "On", "On": "ForceOff"} if tested_computer_system_state not in computer_system_reset_commands.keys(): cts_error("Computer System's state neither \"Off\" nor \"On\"") self.set_validation_status(ValidationStatus.FAILED) return else: # by substracting Current System state from {"On", "Off"} set we get the state we expect the Computer System # to be in after Reset command expected_computer_system_state = list(set(computer_system_reset_commands.keys()) - {tested_computer_system_state})[0] print "MESSAGE::Will try to Power %s Computer System %s " % (expected_computer_system_state, tested_computer_system.odata_id) status, status_code, response_body, _ = \ api_caller.post_resource("%s/Actions/ComputerSystem.Reset" % tested_computer_system.url, discovery_container, payload={ "ResetType": "%s" % computer_system_reset_commands[ tested_computer_system_state]}, acceptable_return_codes=[ ReturnCodes.NO_CONTENT], api_endpoint_override=tested_computer_system.netloc) if status: print "MESSAGE::Reset action accepted by server" else: cts_error("Incorrect status code {status_code}, response body: {response_body:response_body}", **locals()) self.set_validation_status(ValidationStatus.FAILED) return print ("MESSAGE::Waiting up to %ss for the Computer System to change its state to %s" % (test_timeout, expected_computer_system_state)) test_duration = 0 while test_duration < test_timeout: sleep(1) test_duration += 1 link, status, status_code, response_body, _ = \ api_caller.get_resource(tested_computer_system.url, discovery_container, api_endpoint_override=tested_computer_system.netloc) if not status: cts_error("Could not get {tested_computer_system} info from the server; status code: {status_code}; " + "response body {response_body:response_body}", **locals()) self.set_validation_status(ValidationStatus.FAILED) return if response_body["PowerState"] == expected_computer_system_state: print "Message::Computer System has successfully change its state" self.set_validation_status(ValidationStatus.PASSED) return cts_error("Computer System did not change its state in time") self.set_validation_status(ValidationStatus.FAILED) return