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 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 run(self): print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) discovery_container, status = api_explorer.discover( MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) print "TEST_CASE::Checking for mandatory entities" requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1) ] preconditions = Preconditions(self.metadata_container, requirements) self.set_validation_status(preconditions.validate(discovery_container)) validation_functions = [ self.at_least_one_compute_module_in_pod, self.at_least_one_ethernet_switch_in_the_pod, self.ethernet_based_fabric_for_network_connectivity, self.secure_communication_channel, self.node_reset_support, self.manual_tests, self.validate_all_or_none_chassis_have_location, self.validate_path_using_contains_property, self.power_monitoring_support ] for validation_function in validation_functions: try: validation_function(discovery_container) except Exception as err: print "ERROR::Exception %s" % err self.set_validation_status(ValidationStatus.FAILED) print "SCREEN::Overall status: %s" % self.validation_status
def test_not_found_in_empty_discovery_container(self): discovery_container = DiscoveryContainer(metadata_container=self.metadata_container) requirements = [ Requirement("Chassis.Chassis", min=1, max=1) ] preconditions = Preconditions(self.metadata_container, requirements) self.assertEqual(ValidationStatus.FAILED, preconditions.validate(discovery_container))
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 __init__(self, metadata_container, requirements=None, ignore=None, skip_check_predicate=None): """ :type metadata_container: cts_core.metadata.metadata_container.MetadataContainer :type requirements: list(Requirement) """ self._metadata_container = metadata_container self._preconditions = Preconditions(metadata_container, requirements, skip_check_predicate=skip_check_predicate) self._ignore = ignore if ignore is not None else set()
def test_not_found_in_non_empty_discovery_container(self): discovery_container = DiscoveryContainer(metadata_container=self.metadata_container) discovery_container.add_resource(ApiResource("id_1", 'netloc', {'@odata.id': '123', 'Oem': {'Intel_RackScale': {'Location': {'Id': 1, 'ParentId': None} } } }, None)) requirements = [ Requirement("Chassis.Chassis", min=1, max=1) ] preconditions = Preconditions(self.metadata_container, requirements) self.assertEqual(ValidationStatus.FAILED, preconditions.validate(discovery_container))
def test_fail_when_more_than_max(self): discovery_container = DiscoveryContainer(metadata_container=self.metadata_container) discovery_container.add_resource(ApiResource("id_1", 'netloc', {'@odata.id': '123', '@odata.type' : '#Chassis.v1_0_0.Chassis', 'Oem': {'Intel_RackScale': {'Location': {'Id': 1, 'ParentId': None} } } }, None)) requirements = [ Requirement("Chassis.Chassis", min=0, max=0) ] preconditions = Preconditions(self.metadata_container, requirements) self.assertEqual(ValidationStatus.FAILED, preconditions.validate(discovery_container))
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 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 __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): if self.metadata_container is None: return print "TEST_CASE::API crawling" api_explorer = ApiExplorer(self.metadata_container, self.configuration) discovery_container, status = api_explorer.discover(MetadataConstants.SERVICE_ROOT_URI, MetadataConstants.SERVICE_ROOT) print "STATUS::{status}".format(status=status) print "TEST_CASE::Checking for mandatory entities" requirements = [ Requirement(MetadataConstants.SERVICE_ROOT, min=1, max=1) ] preconditions = Preconditions(self.metadata_container, requirements) self.set_validation_status(preconditions.validate(discovery_container)) validation_functions = [self.at_least_one_compute_module_in_pod, self.at_least_one_ethernet_switch_in_the_pod, self.ethernet_based_fabric_for_network_connectivity, self.secure_communication_channel, self.node_reset_support, self.manual_tests, self.validate_all_or_none_chassis_have_location, self.validate_path_using_contains_property, self.power_monitoring_support] for validation_function in validation_functions: try: validation_function(discovery_container) except Exception as err: cts_error("Exception {err:exception}", err=err) self.set_validation_status(ValidationStatus.FAILED) print "SCREEN::Overall status: %s" % self.validation_status
class MetadataGetValidator: def __init__(self, metadata_container, requirements=None, ignore=None, skip_check_predicate=None): """ :type metadata_container: cts_core.metadata.metadata_container.MetadataContainer :type requirements: list(Requirement) """ self._metadata_container = metadata_container self._preconditions = Preconditions(metadata_container, requirements, skip_check_predicate=skip_check_predicate) self._ignore = ignore if ignore is not None else set() def validate(self, discovery_container): """ :type discovery_container: cts_core.discovery.discovery_container.DiscoveryContainer :rtype: cts_core.validation.validation_status.ValidationStatus """ print "TEST_CASE::Checking for mandatory entities" status = self._preconditions.validate(discovery_container) print "STATUS::%s" % status resources = discovery_container.itervalues() count = len(discovery_container) for idx, api_resource in enumerate(resources): print "TEST_CASE::%s" % api_resource.odata_id print "MESSAGE::[%5d/%5d] Checking %s : %s" % ( idx + 1, count, api_resource.odata_id, api_resource.odata_type) api_resource_status = self.validate_single_entity(api_resource) print "STATUS::%s" % api_resource_status status = ValidationStatus.join_statuses(status, api_resource_status) print "SCREEN::Overall status: %s" % status return status def get_validated_information(self, discovery_container): resources = discovery_container.itervalues() ValidatedEntity = collections.namedtuple('ValidatedEntity', 'test_case count checking message status') list_of_validated_elements = [] for idx, api_resource in enumerate(resources): api_resource_status = self.validate_single_entity(api_resource) list_of_validated_elements.append(ValidatedEntity(test_case=api_resource.odata_id, count=idx + 1, checking=api_resource.odata_id, message=api_resource.odata_type, status=api_resource_status)) return list_of_validated_elements def validate_single_entity(self, api_resource): """ :type api_resource: cts_core.discovery.api_resource.ApiResourceProxy :rtype: str """ if not api_resource.body: cts_warning("{odata_id:id} can't be analyzed - body is empty", api_resource.odata_id) return ValidationStatus.FAILED try: entity_details = self._metadata_container.entities[api_resource.odata_type] except KeyError: cts_error("Unable to find definition of entity {expected_odata_type} for resource {odata_id:id}", expected_odata_type=api_resource.odata_type, odata_id=api_resource.odata_id) return ValidationStatus.FAILED return entity_details.validate(resource=api_resource.body, resource_path=api_resource.odata_id, odata_type=api_resource.odata_type)
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()
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()
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
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()