def _parse_ignore_types(self): if len(self.configuration.IgnoreTypes): self._ignore_types = set([type.strip() for type in self.configuration.IgnoreTypes.split(",")]) print "WARNING::CTS is configured to skip properties and entities of the following " \ "types:" for ignore_type in self._ignore_types: cts_warning("\t- {ignore_type}", ignore_type=ignore_type)
def __init__(self, url, netloc, json_body, expected_odata_type): """ :param url: URL of api resource to add :type url: str :param netloc: address and port of the API service that owns the resource :type netloc: str :param json_body: Python representation of API resource :type json_body: OrderedDict :param expected_odata_type: When CTS issues GET request, it usually expects a resource with a specific @odata.type :type expected_odata_type: str """ self.url = url self.netloc = netloc self.odata_id = json_body.get(".".join([ODATA, ID])) self.odata_type = sanitize_odata_type( json_body.get(".".join([ODATA, TYPE]))) if self.odata_type is None: self.odata_type = sanitize_odata_type(expected_odata_type) self.expected_odata_type = sanitize_odata_type(expected_odata_type) self.body = json_body self.location = ApiResource._get_location(json_body) self.contains = ApiResource._get_contains(json_body) self.contained_by = ApiResource._get_contained_by(json_body) self.parent_url = self._get_parent_url() if not self.odata_id: cts_warning( "@odata.id property not present in {body:response_body}. Setting 'None'.", body=json_body) if not self.odata_type: cts_warning( "@odata.type property not present in {body:response_body}. Setting 'None'.", body=json_body)
def _load_metadata(self): qualifiers, services = self.get_services_and_qualifiers() metadata_manager = MetadataManager(qualifiers, ignore_types=self.ignore_types, map_types=self.map_types) if self.configuration.MetadataDirectory: cts_warning("CTS is configured to use custom metadata from: {dir}", dir=self.configuration.MetadataDirectory) metadata_container = self._load_custom_metadata( metadata_manager, self.configuration.MetadataDirectory) elif self.configuration.RedfishMetadata: cts_warning( "CTS is configured to use Redfish metadata: {metadata_version}", metadata_version=self.configuration.RedfishMetadata) metadata_container = \ self._load_custom_metadata(metadata_manager, '/'.join((Constants.METADATA_REDFISH_DIR, str(self.configuration.RedfishMetadata)))) else: metadata_container = self._load_built_in_metadata( metadata_manager, services) if metadata_container is not None: metadata_container.print_types() return metadata_container
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
def request(cls, http_method, url, **kwargs): if not cls.replay_mode_on(): return None if cls._cursor < len(cls._http_request_ids): _method, _url, _request, _response, _status_code = HttpRequestDAO.retrieve(cls._http_request_ids[cls._cursor]) if url != _url: cts_error("{url:id} requested url={url} is different than url={_url} " + "that is stored in the database. Quitting Replay mode.", **locals()) sys.exit("Replay Error") try: request_obj_from_db = json.loads(_request) except (JSONDecodeError, ValueError) as err: request_obj_from_db = {} if not dictionaries_equal(kwargs, request_obj_from_db): request = json.dumps(sanitize_json(kwargs)) cts_warning("{url:id} request {request} is different than request {_request} from database. ", url=url, request=sanitize_json(kwargs), _request=sanitize_json(request_obj_from_db)) sys.exit("Replay Error") cls._cursor += 1 return _response else: cts_error("Reached end of recording. Quitting Replay mode.") cls._replay_mode_on = False return None
def __init__(self, url, netloc, json_body, expected_odata_type): """ :param url: URL of api resource to add :type url: str :param netloc: address and port of the API service that owns the resource :type netloc: str :param json_body: Python representation of API resource :type json_body: OrderedDict :param expected_odata_type: When CTS issues GET request, it usually expects a resource with a specific @odata.type :type expected_odata_type: str """ self.url = url self.netloc = netloc self.odata_id = json_body.get(".".join([ODATA, ID])) self.odata_type = sanitize_odata_type(json_body.get(".".join([ODATA, TYPE]))) if self.odata_type is None: self.odata_type = sanitize_odata_type(expected_odata_type) self.expected_odata_type = sanitize_odata_type(expected_odata_type) self.body = json_body self.location = ApiResource._get_location(json_body) self.contains = ApiResource._get_contains(json_body) self.contained_by = ApiResource._get_contained_by(json_body) self.parent_url = self._get_parent_url() if not self.odata_id: cts_warning("@odata.id property not present in {body:response_body}. Setting 'None'.", body=json_body) if not self.odata_type: cts_warning("@odata.type property not present in {body:response_body}. Setting 'None'.", body=json_body)
def map_type(self, type): if type in self._map_types: mapped_type = self._map_types[type] cts_warning("User declared to use {mapped} to validate original", mapped=mapped_type, original=type) return mapped_type return type
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_types_consistency(self, resource, resource_path, odata_type=None): """ :type resource: dict :type resource_path: str :type odata_type: str :return: validation_status, type that will be validated :rtype: str, str """ if not odata_type: try: odata_type = get_odata_type(resource) except KeyError: odata_type = None if not odata_type: cts_warning("Missing odata.type property in {odata_id:id}", odata_id=resource_path) return ValidationStatus.PASSED_WITH_WARNINGS, self.name else: reported_odata_type = self.metadata_container.map_type(odata_type) if self.metadata_container.to_be_ignored(odata_type, reported_odata_type): return ValidationStatus.PASSED, reported_odata_type try: if self.is_compatible(reported_odata_type): msg = ( "DEBUG::Resource {odata_id} reported entity type {reported_odata_type}, " + "expected type {expected_entity_type}, types are consistent" ).format(odata_id=resource_path, reported_odata_type=reported_odata_type, expected_entity_type=self.name) print msg return ValidationStatus.PASSED, reported_odata_type else: cts_error( "Resource {odata_id:id} reported entity type {reported_odata_type}, " + "expected type {expected_entity_type}, types are not consistent", odata_id=resource_path, reported_odata_type=reported_odata_type, expected_entity_type=self.name) return ValidationStatus.FAILED, reported_odata_type except KeyError: cts_error( "Resource {odata_id:id} reported unknown entity type {reported_odata_type}," + "will process validation with expected entity type {expected_entity_type}", odata_id=resource_path, reported_odata_type=reported_odata_type, expected_entity_type=self.name) return ValidationStatus.FAILED, reported_odata_type
def post_resource(self, url, discovery_container, payload=None, acceptable_return_codes=None, wait_if_async=True, expect_location=None, api_endpoint_override=None): """ Sends http POST request to remote endpoint. Throws AsyncOperation if service created task in response to this request. :type url: str :type discovery_container: DiscoveryContainer :type payload: dict :type acceptable_return_codes: list(int) :type wait_if_async: bool """ from cts_core.discovery.api_explorer import ApiExplorer if expect_location is None: expect_location = True _, status, status_code, response_body, headers = \ self._perform_call(url, http_method=HttpMethods.POST, payload=payload, acceptable_return_codes=acceptable_return_codes, api_endpoint_override=api_endpoint_override) # determine if the operation was completed synchronously or asynchronously if status_code == ReturnCodes.ACCEPTED: if not wait_if_async: raise AsyncOperation() status, status_code, response_body, headers = self._wait_for_task(headers, discovery_container, acceptable_ret_codes=acceptable_return_codes) if expect_location and status and not self._skip_refresh_after_request(url): try: # add the created element and discover all its children new_url = self.links_factory.get_resource_link(headers["Location"], api_endpoint_override=api_endpoint_override).link print "MESSAGE::Refreshing {} info".format(new_url) # refresh the collection info _, get_status, get_status_code, _, _ = self.get_resource(url, discovery_container, api_endpoint_override=api_endpoint_override, check_resource_against_metadata=True) _, rediscovery_status = ApiExplorer(discovery_container.metadata_container, self._config_property_reader).discover(new_url, discovery_container.get_expected_odata_type_for_url(new_url), discovery_container) if get_status and rediscovery_status: print "MESSAGE::Refreshed %s and its children info" % new_url else: cts_warning("Refreshing {odata_id:id} after POST generated errors. Get status code: {code}", odata_id=new_url, code=get_status_code) except (KeyError, TypeError) as err: cts_warning( "POST {odata_id:id} Response has no 'Location' header; Error: {err:exception}", odata_id=url, err=err) return status, status_code, response_body, headers
def _parse_ignore_types(self): self._ignore_types = set([ type.strip() for type in self.configuration.IgnoreTypes.split(",") ]) if len(self._ignore_types) > 0: print "WARNING::CTS is configured to skip properties and entities of the following " \ "types:" for ignore_type in self._ignore_types: cts_warning("\t- {ignore_type}", ignore_type=ignore_type)
def to_be_ignored(self, *types): ignored_types = self.get_ignored_types for type in set(types): if self._wide_types(type, ignored_types): return True elif type in ignored_types: cts_warning("User declared to skip validation of type {type}", type=type) return True return False
def _precondition_check(self, discovery_container): """ :type discovery_container: cts_core.discovery.discovery_container.DiscoveryContainer :rtype: cts_core.validation.validation_status.ValidationStatus """ status = ValidationStatus.PASSED if self.requirements is None: return status for requirement in self.requirements: if requirement.base not in self._metadata_container.entities: cts_error("Internal Error. Type {base} not found in metadata", base=requirement.base) status = ValidationStatus.FAILED discovered = dict() for requirement in self.requirements: discovered[requirement.base] = discovery_container.count_resources( requirement.base, any_child_version=True) for requirement in self.requirements: if self._skip_check_predicate and self._skip_check_predicate( discovery_container, requirement): continue if (requirement.optional is True and discovered.setdefault( requirement.base, 0) < requirement.min): cts_warning( "Minimum number of {type} is {min} to make this tests. Current number: {current}. " "Tests will be skipped", type=requirement.base, min=requirement.min, current=discovered[requirement.base]) status = ValidationStatus.PASSED_WITH_WARNINGS continue if (requirement.min is not None and discovered.setdefault( requirement.base, 0) < requirement.min): cts_error( "Minimum number of {type} is {min}. Current number: {current}", type=requirement.base, min=requirement.min, current=discovered[requirement.base]) status = ValidationStatus.FAILED if (requirement.max is not None and discovered.setdefault( requirement.base, 0) > requirement.max): cts_error( "Maximum number of {type} is {max}. Current number: {current}", type=requirement.base, max=requirement.max, current=discovered[requirement.base]) status = ValidationStatus.FAILED return status
def _wide_types(type, ignored_types): import re for i in ignored_types: prog = re.compile(i) if prog.match(type): cts_warning("User declared to skip validation of type {type} using this pattern {pattern}", type=type, pattern=prog.pattern) return True return False
def _validate_property_list(self, context, variable_path, property_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: status = ValidationStatus.join_statuses( self._validate_property(context, variable_path, property_description), 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 _wide_types(type, ignored_types): import re for i in ignored_types: prog = re.compile(i) if prog.match(type): cts_warning( "User declared to skip validation of type {type} using this pattern {pattern}", type=type, pattern=prog.pattern) return True return False
def patch_resource(self, url, discovery_container, payload=None, acceptable_return_codes=None, wait_if_async=True, api_endpoint_override=None): """ Sends http PATCH request to remote endpoint. Throws AsyncOperation if service created task in response to this request. :type url: str :type discovery_container: DiscoveryContainer :type payload: dict :type acceptable_return_codes: list(int) :type wait_if_async: bool """ _, patch_status, patch_status_code, response_body, headers = \ self._perform_call(url, http_method=HttpMethods.PATCH, payload=payload, acceptable_return_codes=acceptable_return_codes, api_endpoint_override=api_endpoint_override) # determine if the operation was completed synchronously or asynchronously if patch_status_code == ReturnCodes.ACCEPTED: if not wait_if_async: raise AsyncOperation() patch_status, patch_status_code, response_body, headers = self._wait_for_task( headers, discovery_container, acceptable_ret_codes=acceptable_return_codes) if patch_status != RequestStatus.SUCCESS: cts_error("{url:id} Patch failed. Status code: {code}", url=url, code=patch_status_code) print "MESSAGE::Refreshing {} info".format(url) _, get_status, get_status_code, _, _ = self.get_resource( url, discovery_container, api_endpoint_override=api_endpoint_override, check_resource_against_metadata=True) if not get_status: cts_warning( "Refreshing {url:id} after PATCH generated errors. Get status code: {code}", url=url, code=get_status_code) return patch_status, patch_status_code, response_body, headers
def add_resource(self, resource, check_resource_against_metadata=None): """ :type resource: cts_core.discovery.api_resource.ApiResource :return: cts_framework.commons.enums.RequestStatus """ if resource is None: return if resource.odata_id is None: return self[resource.url] = resource odata_type = resource.odata_type if odata_type: # do not add the resource to any table if it is of an unknown type self.register_url_pattern(resource.url, odata_type) resource.expected_odata_type = odata_type new_row = json_normalize(resource.body) new_row["url"] = resource.url if odata_type in self._dataframes: # delete the deprecated definition of the resource if it exists if len(self._dataframes[odata_type] [self._dataframes[odata_type]["url"] == resource.url].index): self._dataframes[odata_type] = \ self._dataframes[odata_type][self._dataframes[odata_type]["url"] != resource.url] self._dataframes[odata_type] = \ concat([self._dataframes[odata_type], new_row], axis=0, ignore_index=True) else: self._dataframes[odata_type] = new_row if not check_resource_against_metadata: return RequestStatus.SUCCESS # this method is called in ApiCaller.get_resource() method, hence RequestStatus as a return type if self.metadata_container: status = MetadataGetValidator( self.metadata_container).validate_single_entity(resource) if status == ValidationStatus.PASSED: print "DEBUG::Metadata validation for resource {} passed".format( resource.url) return RequestStatus.SUCCESS else: cts_error( "Resource {odata_id:id} did not pass metadata validation", odata_id=resource.url) return RequestStatus.FAILED else: cts_warning("Metadata not known, skipping resource validation") return RequestStatus.SUCCESS
def delete_resource(self, url, discovery_container, acceptable_return_codes=None, wait_if_async=True, api_endpoint_override=None): """ Sends http DELETE request to remote endpoint. Throws AsyncOperation if service created task in response to this request. :type url: str :type discovery_container: DiscoveryContainer :type acceptable_return_codes: list(int) :type wait_if_async: bool """ link, status, status_code, response_body, headers = \ self._perform_call(url, http_method=HttpMethods.DELETE, acceptable_return_codes=acceptable_return_codes, api_endpoint_override=api_endpoint_override) # determine if the operation was completed synchronously or asynchronously if status_code == ReturnCodes.ACCEPTED: if not wait_if_async: raise AsyncOperation() status, status_code, response_body, headers = self._wait_for_task( headers, discovery_container, acceptable_ret_codes=acceptable_return_codes) # In UNPICKLE mode, CTS can't find a parent resource, so return mocked success if getenv('CTS_UNPICKLE', None): return MockConstants.delete_resource() if status == RequestStatus.SUCCESS: if discovery_container[link.link].parent_url: self.get_resource(discovery_container[link.link].parent_url, discovery_container, api_endpoint_override=api_endpoint_override) else: cts_warning( "Unable to update the parent of {url:id} - parent unknown", url=link.link) discovery_container.delete_resource(link.link) else: # not deleted. refresh self.get_resource(url, discovery_container, api_endpoint_override=api_endpoint_override) return status, status_code, response_body, headers
def _wait_for_task(self, headers, discovery_container, api_endpoint_override=None, acceptable_ret_codes=None): if acceptable_ret_codes is None: acceptable_ret_codes = [ ReturnCodes.OK, ReturnCodes.CREATED, ReturnCodes.NO_CONTENT ] task_monitor = headers.get('Location') print "MESSAGE::Waiting for task completion. Monitor = %s" % task_monitor if task_monitor: for _ in range(0, TASK_TIMEOUT): _, status, status_code, task, headers = \ self.get_resource(task_monitor, discovery_container, acceptable_ret_codes + [ ReturnCodes.ACCEPTED ], api_endpoint_override) # The client may also cancel the operation by performing a DELETE on the Task resource. Deleting the # Task resource object may invalidate the associated Task Monitor and subsequent GET on the Task # Monitor URL returns either 410 (Gone) or 404 (Not Found) if status_code in [ReturnCodes.NOT_FOUND, ReturnCodes.GONE]: cts_warning("Task was cancelled unexpectedly") return RequestStatus.FAILED, None, None, None # Once the operation has completed, the Task Monitor shall return a status code of OK (200) or # CREATED (201) for POST and include the headers and response body of the initial operation, as if it # had completed synchronously if status_code in acceptable_ret_codes: return status, status_code, task, headers # As long as the operation is in process, the service shall continue to return a status code of # 202 (Accepted) when querying the Task Monitor returned in the location header if status_code not in [ReturnCodes.ACCEPTED]: cts_error( "{odata_id:id} Task monitor returned unexpected status code: {code}", odata_id=task_monitor, code=status_code) return RequestStatus.FAILED, None, None, None print "MESSAGE::Task in progress. Waiting for completion" time.sleep(1) else: cts_error( "{odata_id:id} Task has been created but Location header not found", odata_id=task_monitor) return RequestStatus.FAILED, None, None, None
def validate(self, resource, resource_path, annotations=None, odata_type=None): """ :type annotations: dict :type resource: dict :type resource_path: str :type odata_type: str :rtype: str """ if not odata_type: try: odata_type = get_odata_type(resource) except KeyError: odata_type = None if not odata_type: cts_warning( "Resource {odata_id} did not report odata.type property", odata_id=resource_path) return ValidationStatus.PASSED_WITH_WARNINGS else: mapped_odata_type = self.metadata_container.map_type(odata_type) if self.metadata_container.to_be_ignored(odata_type, mapped_odata_type): return ValidationStatus.PASSED types_consistency_validation_status, entity_type = self._validate_types_consistency( resource, resource_path, odata_type) if entity_type == self.name: resource_validation_status = self._validate_container( resource, resource_path) else: try: resource_validation_status = self.metadata_container.entities[ entity_type]._validate_container(resource, resource_path) except KeyError: resource_validation_status = ValidationStatus.FAILED except Exception as error: cts_error("{id:id} Unexpected error: {error:exception}", id == resource_path, error=error) resource_validation_status = ValidationStatus.FAILED return ValidationStatus.join_statuses( types_consistency_validation_status, resource_validation_status)
def tree_deep_search(body, key): """ Use this method for looking a last property on the tree :return ValidationStatus """ resource_tree = objectpath.Tree(json.loads(json.dumps(body))) result_tuple = tuple(resource_tree.execute('$..{}'.format(key))) if not result_tuple: cts_warning("\t{status} Key {key} was not found".format( status=get_warning(), key=key)) return ValidationStatus.PASSED_WITH_WARNINGS cts_message("\t{status} Key {key} was found".format(status=get_ok(), key=key)) return ValidationStatus.PASSED
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
def _parse_service_types_override(self): self._service_types_override = {service for service in (type.strip() for type in self.configuration.ServiceTypeOverride.split(",")) if len(service) > 0 } illegal = self._service_types_override - set(ServiceTypes.all()) if len(illegal) > 0: cts_error("Invalid service types specified in ServiceTypeOverride configuration parameter: {illegal}", illegal=", ".join(illegal)) self._service_types_override -= illegal if len(self._service_types_override) > 0: cts_warning("Test was designed to be run against metadata for: {registered}, " + "but user declared that service supports: {override}", registered=self.registered_services_s(), override=", ".join(self._service_types_override))
def add_resource(self, resource, check_resource_against_metadata=None): """ :type resource: cts_core.discovery.api_resource.ApiResource :return: cts_framework.commons.enums.RequestStatus """ if resource is None: return if resource.odata_id is None: return self[resource.url] = resource odata_type = resource.odata_type if odata_type: # do not add the resource to any table if it is of an unknown type self.register_url_pattern(resource.url, odata_type) resource.expected_odata_type = odata_type new_row = json_normalize(resource.body) new_row["url"] = resource.url if odata_type in self._dataframes: # delete the deprecated definition of the resource if it exists if len(self._dataframes[odata_type][self._dataframes[odata_type]["url"] == resource.url].index): self._dataframes[odata_type] = \ self._dataframes[odata_type][self._dataframes[odata_type]["url"] != resource.url] self._dataframes[odata_type] = \ concat([self._dataframes[odata_type], new_row], axis=0, ignore_index=True) else: self._dataframes[odata_type] = new_row if not check_resource_against_metadata: return RequestStatus.SUCCESS # this method is called in ApiCaller.get_resource() method, hence RequestStatus as a return type if self.metadata_container: status = MetadataGetValidator(self.metadata_container).validate_single_entity(resource) if status == ValidationStatus.PASSED: print "DEBUG::Metadata validation for resource {} passed".format(resource.url) return RequestStatus.SUCCESS else: cts_error("Resource {odata_id:id} did not pass metadata validation", odata_id=resource.url) return RequestStatus.FAILED else: cts_warning("Metadata not known, skipping resource validation") return RequestStatus.SUCCESS
def _validate_types_consistency(self, resource, resource_path, odata_type=None): """ :type resource: dict :type resource_path: str :type odata_type: str :return: validation_status, type that will be validated :rtype: str, str """ if not odata_type: try: odata_type = get_odata_type(resource) except KeyError: odata_type = None if not odata_type: cts_warning("Missing odata.type property in {odata_id:id}", odata_id=resource_path) return ValidationStatus.PASSED_WITH_WARNINGS, self.name else: reported_odata_type = self.metadata_container.map_type(odata_type) if self.metadata_container.to_be_ignored(odata_type, reported_odata_type): return ValidationStatus.PASSED, reported_odata_type try: if self.is_compatible(reported_odata_type): msg = ("DEBUG::Resource {odata_id} reported entity type {reported_odata_type}, " + "expected type {expected_entity_type}, types are consistent").format( odata_id=resource_path, reported_odata_type=reported_odata_type, expected_entity_type=self.name) print msg return ValidationStatus.PASSED, reported_odata_type else: cts_error("Resource {odata_id:id} reported entity type {reported_odata_type}, " + "expected type {expected_entity_type}, types are not consistent", odata_id=resource_path, reported_odata_type=reported_odata_type, expected_entity_type=self.name) return ValidationStatus.FAILED, reported_odata_type except KeyError: cts_error("Resource {odata_id:id} reported unknown entity type {reported_odata_type}," + "will process validation with expected entity type {expected_entity_type}", odata_id=resource_path, reported_odata_type=reported_odata_type, expected_entity_type=self.name) return ValidationStatus.FAILED, reported_odata_type
def _check_power(self, chassis, discovery_container, error): """Check if power monitoring is possible via 'Power' property """ print "MESSAGE:: Checking Power property" power_control_ok = False try: power_id = chassis.body["Power"]["@odata.id"] power = discovery_container.odataid_lookup(power_id) if not power: cts_error(" Unknown Power referenced: {id:id}", id=power_id) return True, False power_controls_ids = filter(None, [ m.get("@odata.id", None) for m in power[0].body.get("PowerControl", list()) ]) power_controls = discovery_container.odataid_lookup( *power_controls_ids) for power_control in power_controls: power_control_health = "" try: for power_control_body in power_control.body[ "PowerControl"]: power_control_health = power_control_body["Status"][ "Health"] power_consumed_watts = float( power_control_body['PowerConsumedWatts']) if power_consumed_watts > 0: print "MESSAGE:: Found PowerConsumedWatts > 0 in %s.Power [...]" % chassis.odata_id power_control_ok = True # this is it! can monitor power consumption return False, power_control_ok else: cts_warning( " power_consumed_watts = {power_consumed_watts}. Looking for value > 0", **locals()) except KeyError: print "MESSAGE:: PowerConsumedWatts not found in PowerControl: %s" % power_control.odata_id except TypeError: print "ERROR:: Can't get data for {1} Health Status: {0}".format( power_control_health, power_control.odata_id) return True, False except KeyError: print "MESSAGE:: Power property not found in chassis %s" % chassis.odata_id return error, power_control_ok
def _check_power_zones(self, chassis, discovery_container, error): """Check if power monitoring is possible via 'PowerZones' property """ print "MESSAGE:: Checking power zones" power_control_ok = False try: power_zones_id = chassis.body["PowerZones"]["@odata.id"] power_zones = discovery_container.odataid_lookup(power_zones_id) if not power_zones: cts_error(" Unknown PowerZones referenced: {id:id}", id=power_zones_id) return True, False member_ids = filter(None, [ m.get("@odata.id", None) for m in power_zones[0].body.get("Members", list()) ]) power_zones = discovery_container.odataid_lookup(*member_ids) if not power_zones: print "MESSAGE:: No power zone found" for power_zone in power_zones: print "MESSAGE:: Checking power zone %s" % power_zone.odata_id try: power_consumed_watts = float( power_zone.body['PowerConsumedWatts']) if power_consumed_watts > 0: # this is it! can monitor power consumption print "MESSAGE:: Found PowerConsumedWatts > 0 in %s.PowerZones [...]" % chassis.odata_id power_control_ok = True else: cts_warning( " power_consumed_watts = {power_consumed_watts}. Looking for value > 0", **locals()) except KeyError: print "MESSAGE:: PowerConsumedWatts not found in PowerZone: %s" % power_zone.odata_id except TypeError: print "ERROR:: Can't get data for %s. Please check Health Status" % power_zone.odata_id return True, False except KeyError: print "MESSAGE:: PowerZones attribute not found in chassis %s" % ( chassis.odata_id) return error, power_control_ok
def power_monitoring_support(self, discovery_container): """ Test is basing on fact, that without PSME there's no healthy drawer on PODm API. :type discovery_container: cts_core.discovery.discovery_container.DiscoveryContainer :type self.metadata_container: cts_core.metadata.self.metadata_container.MetadataContainer """ print "TEST_CASE::Power monitoring support at Rack level" all_chassis = [ r for r in discovery_container.iter_all(MetadataConstants.CHASSIS) if (r.body["ChassisType"] == "Zone") or ( r.body["ChassisType"] == "Rack") ] if len(all_chassis) == 0: cts_error("Chassis Type: Zone not found") self.set_validation_status(ValidationStatus.FAILED) return overall_error = False overall_result = True for chassis in all_chassis: error = False print "MESSAGE::Checking Rack %s" % chassis.odata_id error, monitoring_supported_via_power_property = self._check_power( chassis, discovery_container, error) error, monitoring_supported_via_power_zones = self._check_power_zones( chassis, discovery_container, error) result = monitoring_supported_via_power_property or monitoring_supported_via_power_zones if result and not error: print "MESSAGE::Rack %s is OK" % chassis.odata_id else: cts_warning("Rack {id:id} does not have power monitoring", id=chassis.odata_id) overall_error = overall_error or error overall_result = overall_result or result if overall_result and not overall_error: self.set_validation_status(ValidationStatus.PASSED) return else: self.set_validation_status(ValidationStatus.FAILED)
def _wait_for_task(self, headers, discovery_container, api_endpoint_override=None, acceptable_ret_codes=None): if acceptable_ret_codes is None: acceptable_ret_codes = [ReturnCodes.OK, ReturnCodes.CREATED, ReturnCodes.NO_CONTENT ] task_monitor = headers.get('Location') print "MESSAGE::Waiting for task completion. Monitor = %s" % task_monitor if task_monitor: for _ in range(0, TASK_TIMEOUT): _, status, status_code, task, headers = \ self.get_resource(task_monitor, discovery_container, acceptable_ret_codes + [ ReturnCodes.ACCEPTED ], api_endpoint_override) # The client may also cancel the operation by performing a DELETE on the Task resource. Deleting the # Task resource object may invalidate the associated Task Monitor and subsequent GET on the Task # Monitor URL returns either 410 (Gone) or 404 (Not Found) if status_code in [ReturnCodes.NOT_FOUND, ReturnCodes.GONE]: cts_warning("Task was cancelled unexpectedly") return RequestStatus.FAILED, None, None, None # Once the operation has completed, the Task Monitor shall return a status code of OK (200) or # CREATED (201) for POST and include the headers and response body of the initial operation, as if it # had completed synchronously if status_code in acceptable_ret_codes: return status, status_code, task, headers # As long as the operation is in process, the service shall continue to return a status code of # 202 (Accepted) when querying the Task Monitor returned in the location header if status_code not in [ReturnCodes.ACCEPTED]: cts_error("{odata_id:id} Task monitor returned unexpected status code: {code}", odata_id=task_monitor, code=status_code) return RequestStatus.FAILED, None, None, None print "MESSAGE::Task in progress. Waiting for completion" time.sleep(1) else: cts_error("{odata_id:id} Task has been created but Location header not found", odata_id=task_monitor) return RequestStatus.FAILED, None, None, None
def _load_metadata(self): qualifiers, services = self.get_services_and_qualifiers() metadata_manager = MetadataManager(qualifiers, ignore_types=self.ignore_types, map_types=self.map_types) if self.configuration.MetadataDirectory: cts_warning("CTS is configured to use custom metadata from: {dir}", dir=self.configuration.MetadataDirectory) metadata_container = self._load_custom_metadata( metadata_manager, self.configuration.MetadataDirectory) else: metadata_container = self._load_built_in_metadata( metadata_manager, services) if metadata_container is not None: metadata_container.print_types() return metadata_container
def _check_resource_list(response_body, pointer, url): cts_warning( "Could not find requestes resource {path} from {url:id} by JSON pointer dereferencing. " "Trying to use alternative, non-RSD based method using requested @odata.id", path=pointer, url=url) dissected_pointer = pointer.rsplit("/", 1) resource_list = resolve_pointer(response_body, dissected_pointer[0]) if isinstance(resource_list, list): requested_resource_odata = url[url.find("/redfish"):] for resource in resource_list: if resource["@odata.id"] == requested_resource_odata: return resource cts_error( "Could not find {path} from {url:id} with search by @odata.id", path=pointer, url=url) return None
def delete_resource(self, url, discovery_container, acceptable_return_codes=None, wait_if_async=True, api_endpoint_override=None): """ Sends http DELETE request to remote endpoint. Throws AsyncOperation if service created task in response to this request. :type url: str :type discovery_container: DiscoveryContainer :type acceptable_return_codes: list(int) :type wait_if_async: bool """ link, status, status_code, response_body, headers = \ self._perform_call(url, http_method=HttpMethods.DELETE, acceptable_return_codes=acceptable_return_codes, api_endpoint_override=api_endpoint_override) # determine if the operation was completed synchronously or asynchronously if status_code == ReturnCodes.ACCEPTED: if not wait_if_async: raise AsyncOperation() status, status_code, response_body, headers = self._wait_for_task(headers, discovery_container, acceptable_ret_codes=acceptable_return_codes) # In UNPICKLE mode, CTS can't find a parent resource, so return mocked success if getenv('CTS_UNPICKLE', None): return MockConstants.delete_resource() if status == RequestStatus.SUCCESS: if discovery_container[link.link].parent_url: self.get_resource(discovery_container[link.link].parent_url, discovery_container, api_endpoint_override=api_endpoint_override) else: cts_warning("Unable to update the parent of {url:id} - parent unknown", url=link.link) discovery_container.delete_resource(link.link) else: # not deleted. refresh self.get_resource(url, discovery_container, api_endpoint_override=api_endpoint_override) return status, status_code, response_body, headers
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 _precondition_check(self, discovery_container): """ :type discovery_container: cts_core.discovery.discovery_container.DiscoveryContainer :rtype: cts_core.validation.validation_status.ValidationStatus """ status = ValidationStatus.PASSED if self.requirements is None: return status for requirement in self.requirements: if requirement.base not in self._metadata_container.entities: cts_error("Internal Error. Type {base} not found in metadata", base=requirement.base) status = ValidationStatus.FAILED discovered = dict() for requirement in self.requirements: discovered[requirement.base] = discovery_container.count_resources(requirement.base, any_child_version=True) for requirement in self.requirements: if self._skip_check_predicate and self._skip_check_predicate(discovery_container, requirement): continue if (requirement.optional is True and discovered.setdefault(requirement.base, 0) < requirement.min): cts_warning("Minimum number of {type} is {min} to make this tests. Current number: {current}. " "Tests will be skipped", type=requirement.base, min=requirement.min, current=discovered[requirement.base]) status = ValidationStatus.PASSED_WITH_WARNINGS continue if (requirement.min is not None and discovered.setdefault(requirement.base, 0) < requirement.min): cts_error("Minimum number of {type} is {min}. Current number: {current}", type=requirement.base, min=requirement.min, current=discovered[requirement.base]) status = ValidationStatus.FAILED if (requirement.max is not None and discovered.setdefault(requirement.base, 0) > requirement.max): cts_error("Maximum number of {type} is {max}. Current number: {current}", type=requirement.base, max=requirement.max, current=discovered[requirement.base]) status = ValidationStatus.FAILED return status
def patch_resource(self, url, discovery_container, payload=None, acceptable_return_codes=None, wait_if_async=True, api_endpoint_override=None): """ Sends http PATCH request to remote endpoint. Throws AsyncOperation if service created task in response to this request. :type url: str :type discovery_container: DiscoveryContainer :type payload: dict :type acceptable_return_codes: list(int) :type wait_if_async: bool """ _, patch_status, patch_status_code, response_body, headers = \ self._perform_call(url, http_method=HttpMethods.PATCH, payload=payload, acceptable_return_codes=acceptable_return_codes, api_endpoint_override=api_endpoint_override) # determine if the operation was completed synchronously or asynchronously if patch_status_code == ReturnCodes.ACCEPTED: if not wait_if_async: raise AsyncOperation() patch_status, patch_status_code, response_body, headers = self._wait_for_task(headers, discovery_container, acceptable_ret_codes=acceptable_return_codes) if patch_status != RequestStatus.SUCCESS: cts_error("{url:id} Patch failed. Status code: {code}", url=url, code=patch_status_code) print "MESSAGE::Refreshing {} info".format(url) _, get_status, get_status_code, _, _ = self.get_resource(url, discovery_container, api_endpoint_override=api_endpoint_override, check_resource_against_metadata=True) if not get_status: cts_warning("Refreshing {url:id} after PATCH generated errors. Get status code: {code}", url=url, code=get_status_code) return patch_status, patch_status_code, response_body, headers
def _parse_map_types(self): if self.configuration.MapTypes: try: self._map_types = {from_type: to_type for (from_type, to_type) in \ ((pair[0].strip(), pair[1].strip()) for pair in \ (pair.split(':') for pair in self.configuration.MapTypes.split(',')) \ ) if len(from_type) > 0 and len(to_type) > 0 } if len(self._map_types) > 0: print "WARNING::CTS is configured to use the following type mappings to " \ "validate instances of unknown types:" for from_type in self._map_types: cts_warning("\t- {from_type}:{to_type}", from_type=from_type, to_type=self._map_types[from_type]) except Exception as error: cts_error("Error while parsing 'MapTypes' configuration parameter. error = {error}", error=error) else: self._map_types = dict()
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 handle_nontrivial(self, context, variable_path, property_description): """ :param context: :type context: :param variable_path: :type variable_path: :param property_description: :type property_description: :return: :rtype: """ applicability, validation_result = self.patch_nontrivial(context.api_resource, variable_path, property_description) if applicability in [ApplicabilityTestResult.SKIP_PROPERTY, ApplicabilityTestResult.NOT_MATCHED]: cts_warning("Skipping non-trivial property {name}", name=property_description) return ApplicabilityTestResult.SKIP_PROPERTY, ValidationStatus.PASSED_WITH_WARNINGS context.register_patch_attempt(self, property_description.name) return applicability, validation_result
def _parse_map_types(self): if self.configuration.MapTypes: try: self._map_types = {from_type: to_type for (from_type, to_type) in \ ((pair[0].strip(), pair[1].strip()) for pair in \ (pair.split(':') for pair in self.configuration.MapTypes.split(',')) \ ) if len(from_type) > 0 and len(to_type) > 0 } if len(self._map_types) > 0: print "WARNING::CTS is configured to use the following type mappings to " \ "validate instances of unknown types:" for from_type in self._map_types: cts_warning("\t- {from_type}:{to_type}", from_type=from_type, to_type=self._map_types[from_type]) except Exception as error: cts_error( "Error while parsing 'MapTypes' configuration parameter. error = {error}", error=error) else: self._map_types = dict()
def _parse_service_types_override(self): self._service_types_override = { service for service in ( type.strip() for type in self.configuration.ServiceTypeOverride.split(",")) if len(service) > 0 } illegal = self._service_types_override - set(ServiceTypes.all()) if len(illegal) > 0: cts_error( "Invalid service types specified in ServiceTypeOverride configuration parameter: {illegal}", illegal=", ".join(illegal)) self._service_types_override -= illegal if len(self._service_types_override) > 0: cts_warning( "Test was designed to be run against metadata for: {registered}, " + "but user declared that service supports: {override}", registered=self.registered_services_s(), override=", ".join(self._service_types_override))
def _check_power(self, chassis, discovery_container, error): """Check if power monitoring is possible via 'Power' property """ print "MESSAGE:: Checking Power property" power_control_ok = False try: power_id = chassis.body["Power"]["@odata.id"] power = discovery_container.odataid_lookup(power_id) if not power: cts_error(" Unknown Power referenced: {id:id}", id=power_id) return True, False power_controls_ids = filter(None, [m.get("@odata.id", None) for m in power[0].body.get("PowerControl", list())]) power_controls = discovery_container.odataid_lookup(*power_controls_ids) for power_control in power_controls: power_control_health = "" try: for power_control_body in power_control.body["PowerControl"]: power_control_health = power_control_body["Status"]["Health"] power_consumed_watts = float(power_control_body['PowerConsumedWatts']) if power_consumed_watts > 0: print "MESSAGE:: Found PowerConsumedWatts > 0 in %s.Power [...]" % chassis.odata_id power_control_ok = True # this is it! can monitor power consumption return False, power_control_ok else: cts_warning(" power_consumed_watts = {power_consumed_watts}. Looking for value > 0", **locals()) except KeyError: print "MESSAGE:: PowerConsumedWatts not found in PowerControl: %s" % power_control.odata_id except TypeError: print "ERROR:: Can't get data for {1} Health Status: {0}".format( power_control_health, power_control.odata_id) return True, False except KeyError: print "MESSAGE:: Power property not found in chassis %s" % chassis.odata_id return error, power_control_ok
def _load_metadata(self): qualifiers, services = self.get_services_and_qualifiers() metadata_manager = MetadataManager(qualifiers, ignore_types=self.ignore_types, map_types=self.map_types) if self.configuration.MetadataDirectory: cts_warning("CTS is configured to use custom metadata from: {dir}", dir=self.configuration.MetadataDirectory) metadata_container = self._load_custom_metadata(metadata_manager, self.configuration.MetadataDirectory) elif self.configuration.RedfishMetadata: cts_warning("CTS is configured to use Redfish metadata: {metadata_version}", metadata_version=self.configuration.RedfishMetadata) metadata_container = \ self._load_custom_metadata(metadata_manager, '/'.join((Constants.METADATA_REDFISH_DIR, str(self.configuration.RedfishMetadata)))) else: metadata_container = self._load_built_in_metadata(metadata_manager, services) if metadata_container is not None: metadata_container.print_types() return metadata_container
def power_monitoring_support(self, discovery_container): """ Test is basing on fact, that without PSME there's no healthy drawer on PODm API. :type discovery_container: cts_core.discovery.discovery_container.DiscoveryContainer :type self.metadata_container: cts_core.metadata.self.metadata_container.MetadataContainer """ print "TEST_CASE::Power monitoring support at Rack level" all_chassis = [r for r in discovery_container.iter_all(MetadataConstants.CHASSIS) if (r.body["ChassisType"] == "Zone") or (r.body["ChassisType"] == "Rack")] if len(all_chassis) == 0: cts_error("Chassis Type: Zone not found") self.set_validation_status(ValidationStatus.FAILED) return overall_error = False overall_result = True for chassis in all_chassis: error = False print "MESSAGE::Checking Rack %s" % chassis.odata_id error, monitoring_supported_via_power_property = self._check_power(chassis, discovery_container, error) error, monitoring_supported_via_power_zones = self._check_power_zones(chassis, discovery_container, error) result = monitoring_supported_via_power_property or monitoring_supported_via_power_zones if result and not error: print "MESSAGE::Rack %s is OK" % chassis.odata_id else: cts_warning("Rack {id:id} does not have power monitoring", id=chassis.odata_id) overall_error = overall_error or error overall_result = overall_result or result if overall_result and not overall_error: self.set_validation_status(ValidationStatus.PASSED) return else: self.set_validation_status(ValidationStatus.FAILED)
def validate(self, resource, resource_path, annotations=None, odata_type=None): """ :type annotations: dict :type resource: dict :type resource_path: str :type odata_type: str :rtype: str """ if not odata_type: try: odata_type = get_odata_type(resource) except KeyError: odata_type = None if not odata_type: cts_warning("Resource {odata_id} did not report odata.type property", odata_id=resource_path) return ValidationStatus.PASSED_WITH_WARNINGS else: mapped_odata_type = self.metadata_container.map_type(odata_type) if self.metadata_container.to_be_ignored(odata_type, mapped_odata_type): return ValidationStatus.PASSED types_consistency_validation_status, entity_type = self._validate_types_consistency(resource, resource_path, odata_type) if entity_type == self.name: resource_validation_status = self._validate_container(resource, resource_path) else: try: resource_validation_status = self.metadata_container.entities[entity_type]._validate_container(resource, resource_path) except KeyError: resource_validation_status = ValidationStatus.FAILED except Exception as error: cts_error("{id:id} Unexpected error: {error:exception}", id==resource_path, error=error) resource_validation_status = ValidationStatus.FAILED return ValidationStatus.join_statuses(types_consistency_validation_status, resource_validation_status)
def _check_power_zones(self, chassis, discovery_container, error): """Check if power monitoring is possible via 'PowerZones' property """ print "MESSAGE:: Checking power zones" power_control_ok = False try: power_zones_id = chassis.body["PowerZones"]["@odata.id"] power_zones = discovery_container.odataid_lookup(power_zones_id) if not power_zones: cts_error(" Unknown PowerZones referenced: {id:id}", id=power_zones_id) return True, False member_ids = filter(None, [m.get("@odata.id", None) for m in power_zones[0].body.get("Members", list())]) power_zones = discovery_container.odataid_lookup(*member_ids) if not power_zones: print "MESSAGE:: No power zone found" for power_zone in power_zones: print "MESSAGE:: Checking power zone %s" % power_zone.odata_id try: power_consumed_watts = float(power_zone.body['PowerConsumedWatts']) if power_consumed_watts > 0: # this is it! can monitor power consumption print "MESSAGE:: Found PowerConsumedWatts > 0 in %s.PowerZones [...]" % chassis.odata_id power_control_ok = True else: cts_warning(" power_consumed_watts = {power_consumed_watts}. Looking for value > 0", **locals()) except KeyError: print "MESSAGE:: PowerConsumedWatts not found in PowerZone: %s" % power_zone.odata_id except TypeError: print "ERROR:: Can't get data for %s. Please check Health Status" % power_zone.odata_id return True, False except KeyError: print "MESSAGE:: PowerZones attribute not found in chassis %s" % (chassis.odata_id) return error, power_control_ok
def handle_nontrivial(self, context, variable_path, property_description): """ :param context: :type context: :param variable_path: :type variable_path: :param property_description: :type property_description: :return: :rtype: """ applicability, validation_result = self.patch_nontrivial( context.api_resource, variable_path, property_description) if applicability in [ ApplicabilityTestResult.SKIP_PROPERTY, ApplicabilityTestResult.NOT_MATCHED ]: cts_warning("Skipping non-trivial property {name}", name=property_description) return ApplicabilityTestResult.SKIP_PROPERTY, ValidationStatus.PASSED_WITH_WARNINGS context.register_patch_attempt(self, property_description.name) return applicability, validation_result
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()