def __additional_configuration_types_check(cls, configuration, expected_param_type): configuration_vars = vars(configuration) problems_flag = False try: for t in configuration_vars: if configuration_vars[t] != None: if not isinstance(configuration_vars[t], expected_param_type[t]): cts_error("Issue with config file: %s=%s should be %s is %s" % ( t, configuration_vars[t], expected_param_type[t], type(t))) problems_flag = True try: # if conversion fails, get next type to verification con = float(configuration_vars[t]) if not isinstance(con, expected_param_type[t]): cts_error("Issue with config file: %s=%s should be %s is %s" % ( t, con, expected_param_type[t], type(con))) problems_flag = True except: pass except KeyError: pass finally: if problems_flag: exit(1)
def _test_case_get_created_logical_drive(self, logical_drive): print "TEST_CASE::Get the created logical drive" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_logical_drive, self.discovery_container, api_endpoint_override=self.created_logical_drive_netloc) if not status: cts_error("Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED else: print "MESSAGE::Comparing newly created logical drive to given payload" if not JsonComparator.compare_json_to_golden(response_body, self.expected_created_logical_drive_body, ignore = ["/Name"]): # Name setting not supported by PSME REST API cts_error("Newly created logical drive's body incorrect") self.set_status_failed() print "MESSAGE::Proceeding with deleting the resource" return ValidationStatus.FAILED print "MESSAGE::Newly created logical drive's correct" self.set_status_passed() return ValidationStatus.PASSED
def validate_redfish_uris_consistency(self, api_resource, metadata_container): odata_id = api_resource.odata_id odata_type = api_resource.odata_type entity = metadata_container.entities.get(odata_type) if entity is None: mapped_type = metadata_container._map_types.get(odata_type) entity = metadata_container.entities.get(mapped_type) if entity is None: cts_error( "No entity in metadata with @odata.type={odata_type}". format(odata_type=odata_type)) return ValidationStatus.FAILED if Annotation.REDFISH_URIS not in entity._annotations: return ValidationStatus.PASSED redfish_uris_list = entity._annotations[ Annotation.REDFISH_URIS].redfish_uris matched_uris = [ redfish_uri for redfish_uri in redfish_uris_list if re.match(re.sub(r"{.+}", ".+", redfish_uri), odata_id) ] if not matched_uris: cts_error( "Resource {res} does not match expected RedfishUri in metadata. Expected {expected}" .format(res=odata_id, expected=redfish_uris_list)) return ValidationStatus.FAILED else: cts_message( "Resource {res} is compliant with expected RedfishUri in metadata" .format(res=odata_id)) return ValidationStatus.PASSED
def _perform_call(self, url, http_method=None, acceptable_return_codes=None, payload=None, api_endpoint_override=None, format=None): """ Helper method that executes http request and returns result. Internally it may talk to remote API or retrieve recorded data from database. :rtype: (Link, RequestStatus, int, json|String, dict) :type http_method: str :type acceptable_return_codes: list :type payload: object """ if url is None: return None, RequestStatus.FAILED, None, None, None if format is None: format = ApiCaller.FORMAT_JSON if not http_method: http_method = HttpMethods.GET if not acceptable_return_codes: acceptable_return_codes = ApiCaller.DEFAULT_RETURN_CODES[http_method] try: link, kwargs = self._build_request(url, payload, api_endpoint_override=api_endpoint_override, format=format) url = link.link print "MESSAGE::-%s %s" % (http_method, url) if not self._is_controlled_by_framework: self._log_request(kwargs) response = self._do_request(kwargs, url, http_method) if response is None: return None, RequestStatus.FAILED, None, None, None self._register_request(http_method, kwargs, response, url) status = RequestStatus.SUCCESS if response.status_code in acceptable_return_codes else RequestStatus.FAILED status_code, headers = response.status_code, response.headers if status_code in range(500, 600): custom_response_text = HTTPServerErrors.ERROR.get(status_code, "Unknown server error: %s" % status_code) return None, RequestStatus.FAILED, status_code, custom_response_text, None if format == ApiCaller.FORMAT_JSON: response_body = self._decode_json(url, response) if response_body is None: return None, RequestStatus.FAILED, None, None, None else: response_body = response.text except Exception as err: cts_error( "Unknown exception '{err:exception}' on {http_method} resource {url:id} : {" "stack:stacktrace}", stack=format_exc(), **locals()) return None, RequestStatus.FAILED, None, None, None return link, status, status_code, response_body, headers
def do_patch(self, payload): payload_shifted = create_data(self.property_full_path, payload) status, status_code, response_body, headers = self._api_caller.patch_resource( self.api_resource.url, self.discovery_container, payload=payload_shifted, acceptable_return_codes=self.patching_strategy.allowable_return_codes) patch_applied = self.patching_strategy.was_patch_applied(status_code) if status != RequestStatus.SUCCESS: cts_error("{odata_id:id} Patching {property} failed (unexpected status code: {code})", odata_id=self.api_resource.odata_id, property=self.property_description.name, code=status_code) validation_status = ValidationStatus.FAILED elif not patch_applied: cts_error("{odata_id:id} Patching {property} failed (patch not applied); status code: {code}", odata_id=self.api_resource.odata_id, property=self.property_description.name, code=status_code) validation_status = ValidationStatus.FAILED else: validation_status = ValidationStatus.PASSED return validation_status
def _test_case_create_vlan(self, vlan_network_interface_collection): print "TEST_CASE::Create VLAN" status, status_code, response_body, headers = \ self.api_caller.post_resource( vlan_network_interface_collection, self.discovery_container, payload=self.post_vlan_payload) if not status: cts_error("Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False try: self.created_vlan_network_interface = headers["Location"] self.created_vlan_network_interface_netlock = self.discovery_container.get_netloc( vlan_network_interface_collection) print "MESSAGE::Newly created VLAN Network Interface URI: %s" % self.created_vlan_network_interface except KeyError: cts_error("Incorrect response - header shall contain the location of the created resource") self.set_status_failed() return False print "MESSAGE::VLAN Network Interface created" self.set_status_passed() return True
def validate(self, resource, resource_path, annotations=None): """ :type annotations: dict :type resource: float :type resource_path: str :rtype: str """ if annotations is None: annotations = dict() if type(resource) not in [int, float]: cts_error("Value of property {resource_path} is not double type", resource_path=resource_path) return ValidationStatus.FAILED if "Validation.Minimum" in annotations: minimum = float(annotations["Validation.Minimum"].value) else: minimum = float('-inf') if "Validation.Maximum" in annotations: maximum = float(annotations["Validation.Maximum"].value) else: maximum = float('inf') if minimum <= resource <= maximum: return ValidationStatus.PASSED else: cts_error( "Value {value} of property {resource_path} does not match range <{minimum}, {maximum}>.", value=str(resource), resource_path=resource_path, minimum=str(minimum), maximum=str(maximum)) return ValidationStatus.FAILED
def _test_case_create_remote_target(self, remote_target_collection): print "TEST_CASE::Create a remote target" status, status_code, response_body, headers = self.api_caller.post_resource( remote_target_collection, self.discovery_container, payload=self.post_remote_target_payload) if not status: cts_error("Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED try: self.created_remote_target = headers["Location"] self.created_remote_target_netloc = \ self.discovery_container.get_netloc(remote_target_collection) print "MESSAGE::Newly created remote target url %s" % self.created_remote_target except KeyError: cts_error("In header shall be provided location to created resource") self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Remote target created" self.set_status_passed() return ValidationStatus.PASSED
def _validate_additional_property(self, property_name, property_value, resource_path, unused_properties): if self.allow_additional_properties: print "DEBUG::Additional property {odata_id}->{property_name}" \ .format(odata_id=resource_path, property_name=property_name) try_suggest_other_property_names(name=property_name, candidates=unused_properties) odata_type = match_dynamic_property(property_name, self) if odata_type is None: try: odata_type = get_odata_type(property_value) except KeyError: if isinstance(property_value, dict): cts_error("Property {odata_id:id}->{key} is of complex type without @odata_type", odata_id=resource_path, key=property_name) return ValidationStatus.FAILED else: return ValidationStatus.PASSED mapped_odata_type = self.metadata_container.map_type(odata_type) if not self.metadata_container.to_be_ignored(odata_type, mapped_odata_type): return self.metadata_container.types[mapped_odata_type].validate( property_value, resource_path + '->' + property_name) else: cts_error("Property {odata_id:id}->{property_name} is present, but not defined in metadata. " "Entity {entity_name} does not allow additional properties", odata_id=resource_path, property_name=property_name, entity_name=self.name) try_suggest_other_property_names(name=property_name, candidates=unused_properties) status = ValidationStatus.FAILED
def _test_case_get_created_vlan(self): print "TEST_CASE::Get created VLAN" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_vlan_network_interface, self.discovery_container, api_endpoint_override=self.created_vlan_network_interface_netlock) if not status: cts_error("Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False else: print "MESSAGE::Comparing newly created VLAN Network Interface to given payload" # PSME is unable to set "Name" and "VLANEnable" fields properly but only "VLANEnable" is mandatory if not JsonComparator.compare_json_to_golden(response_body, self.post_vlan_payload, ignore=["/VLANEnable"]): cts_error("Newly created VLAN Network Interface's body incorrect") self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface correct" self.set_status_passed() return True
def _perform_call(self, url, http_method=None, acceptable_return_codes=None, payload=None, api_endpoint_override=None, format=None): """ Helper method that executes http request and returns result. Internally it may talk to remote API or retrieve recorded data from database. :rtype: (Link, RequestStatus, int, json|String, dict) :type http_method: str :type acceptable_return_codes: list :type payload: object """ if url is None: return None, RequestStatus.FAILED, None, None, None if format is None: format = ApiCaller.FORMAT_JSON if not http_method: http_method = HttpMethods.GET if not acceptable_return_codes: acceptable_return_codes = ApiCaller.DEFAULT_RETURN_CODES[http_method] try: link, kwargs = self._build_request(url, payload, api_endpoint_override=api_endpoint_override, format=format) url = link.link print "MESSAGE::-%s %s" % (http_method, url) if not self._is_controlled_by_framework: self._log_request(kwargs) response, status_code = self._do_request(kwargs, url, http_method) if response is None: return None, RequestStatus.FAILED, status_code, None, None self._register_request(http_method, kwargs, response, url) status = RequestStatus.SUCCESS if response.status_code in acceptable_return_codes else RequestStatus.FAILED status_code, headers = response.status_code, response.headers if status_code in range(500, 600): custom_response_text = HTTPServerErrors.ERROR.get(status_code, "Unknown server error: %s" % status_code) return None, RequestStatus.FAILED, status_code, custom_response_text, None if format == ApiCaller.FORMAT_JSON: response_body = self._decode_json(url, response) if response_body is None: return None, RequestStatus.FAILED, None, None, None else: response_body = response.text except Exception as err: cts_error( "Unknown exception '{err:exception}' on {http_method} resource {url:id} : {" "stack:stacktrace}", stack=format_exc(), **locals()) return None, RequestStatus.FAILED, None, None, None return link, status, status_code, response_body, headers
def _test_case_create_static_mac(self, static_mac_collection): print "TEST_CASE::Create Static MAC" status, status_code, response_body, headers = self.api_caller.post_resource( static_mac_collection, self.discovery_container, payload=self.post_static_mac_payload) if not status: cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False try: self.created_static_mac = headers["Location"] self.created_static_mac_netloc = self.discovery_container.get_netloc( static_mac_collection) print "MESSAGE::Newly created Static MAC URI: %s" % self.created_static_mac except KeyError: cts_error( "Incorrect response - header shall contain the location of the created resource" ) self.set_status_failed() return False print "MESSAGE::Static MAC created" self.set_status_passed() return True
def validate(self, resource, resource_path, annotations=None): """ :type annotations: dict :type resource: float :type resource_path: str :rtype: str """ if annotations is None: annotations = dict() if type(resource) not in [int, float]: cts_error("Value of property {resource_path} is not double type", resource_path=resource_path) return ValidationStatus.FAILED if "Validation.Minimum" in annotations: minimum = float(annotations["Validation.Minimum"].value) else: minimum = float('-inf') if "Validation.Maximum" in annotations: maximum = float(annotations["Validation.Maximum"].value) else: maximum = float('inf') if minimum <= resource <= maximum: return ValidationStatus.PASSED else: cts_error("Value {value} of property {resource_path} does not match range <{minimum}, {maximum}>.", value=str(resource), resource_path=resource_path, minimum=str(minimum), maximum=str(maximum)) return ValidationStatus.FAILED
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 # 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 = self._metadata_container.entities[ api_resource.odata_type].properties.values() try: 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 _test_case_create_acl(self, acl_collection): print "TEST_CASE::Create ACL" self.created_acl_netloc = self.discovery_container.get_netloc( acl_collection) status, status_code, response_body, headers = self.api_caller.post_resource( acl_collection, self.discovery_container, payload=self.post_acl_payload, api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False try: self.created_acl = headers["Location"] print "MESSAGE::Newly created ACL URI: %s" % self.created_acl except KeyError: cts_error( "Incorrect response - header shall contain the location of the created resource" ) self.set_status_failed() return False if self.created_acl.endswith("/"): self.created_acl += self.created_acl[:-1] print "MESSAGE::ACL created" self.set_status_passed() return True
def _test_case_get_created_static_mac(self): print "TEST_CASE::Get created Static MAC" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_static_mac, self.discovery_container, api_endpoint_override=self.created_static_mac_netloc) if not status: cts_error( "Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False else: print "MESSAGE::Comparing newly created Static MAC to given payload" if not JsonComparator.compare_json_to_golden( response_body, self.post_static_mac_payload): cts_error("Newly created Static MAC's body incorrect") self.set_status_failed() return False print "MESSAGE::Newly created Static MAC correct" self.set_status_passed() return True
def _test_case_create_vlan(self, vlan_network_interface_collection): print "TEST_CASE::Create VLAN" status, status_code, response_body, headers = \ self.api_caller.post_resource( vlan_network_interface_collection, self.discovery_container, payload=self.post_vlan_payload) if not status: cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False try: self.created_vlan_network_interface = headers["Location"] self.created_vlan_network_interface_netlock = self.discovery_container.get_netloc( vlan_network_interface_collection) print "MESSAGE::Newly created VLAN Network Interface URI: %s" % self.created_vlan_network_interface except KeyError: cts_error( "Incorrect response - header shall contain the location of the created resource" ) self.set_status_failed() return False print "MESSAGE::VLAN Network Interface created" self.set_status_passed() return True
def _test_case_get_created_vlan(self): print "TEST_CASE::Get created VLAN" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(self.created_vlan_network_interface, self.discovery_container, api_endpoint_override=self.created_vlan_network_interface_netlock) if not status: cts_error( "Wrong status code of GET response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False else: print "MESSAGE::Comparing newly created VLAN Network Interface to given payload" # PSME is unable to set "Name" and "VLANEnable" fields properly but only "VLANEnable" is mandatory if not JsonComparator.compare_json_to_golden( response_body, self.post_vlan_payload, ignore=["/VLANEnable"]): cts_error( "Newly created VLAN Network Interface's body incorrect") self.set_status_failed() return False print "MESSAGE::Newly created VLAN Network Interface correct" self.set_status_passed() return True
def __additional_configuration_types_check(cls, configuration, expected_param_type): configuration_vars = vars(configuration) problems_flag = False try: for t in configuration_vars: if configuration_vars[t] != None: if not isinstance(configuration_vars[t], expected_param_type[t]): cts_error( "Issue with config file: %s=%s should be %s is %s" % (t, configuration_vars[t], expected_param_type[t], type(t))) problems_flag = True try: # if conversion fails, get next type to verification con = float(configuration_vars[t]) if not isinstance(con, expected_param_type[t]): cts_error( "Issue with config file: %s=%s should be %s is %s" % (t, con, expected_param_type[t], type(con))) problems_flag = True except: pass except KeyError: pass finally: if problems_flag: exit(1)
def validate_configuration(cls, can_run, configuration): for parameter in cls.CONFIGURATION_PARAMETERS: configuration_param_value = getattr(configuration, parameter.name) if parameter.min is not None: if isinstance(configuration_param_value, (int, long, float)): if parameter.type( configuration_param_value) < parameter.min: cts_error( "Configuration parameter {name} should be >= {min}", name=parameter.name, min=parameter.min) can_run = False if parameter.max is not None: if isinstance(configuration_param_value, (int, long, float)): if parameter.type( configuration_param_value) > parameter.max: cts_error( "Configuration parameter {name} should be <= {max}", name=parameter.name, max=parameter.max) can_run = False return can_run
def compare_jsons(json_a, json_b, hide_errors=None): """ :return: comparison status :rtype: bool """ if type(json_a) != type(json_b): # considering string is the same as unicode for jsons if not (isinstance(json_a, basestring) and isinstance(json_b, basestring)): cts_error( "Mismatch between {a} ({a_type}) and {b} ({b_type}) types", a=json_a, a_type=type(json_a), b=json_b, b_type=type(json_b)) return False try: compare_func = { dict: JsonComparator.compare_dicts, list: JsonComparator.compare_lists }[type(json_a)] except KeyError: compare_func = JsonComparator.compare_variables return compare_func(json_a, json_b, hide_errors=hide_errors)
def compare_dicts(dict_a, dict_b, hide_errors=None): """ :dict dictA: dict[] :dict dictB: dict[] :rtype: bool """ if len(dict_a.keys()) != len(dict_b.keys()): if not hide_errors: cts_error( "Dictionaries {dict_a!r} and {dict_b!r} have different keys numbers", **locals()) return False status = True for key in dict_a.keys(): try: if not JsonComparator.compare_jsons( dict_a[key], dict_b[key], hide_errors=hide_errors): status = False except KeyError: if not hide_errors: cts_error( "Key {key} present in dictionary {dict_a!r} but not present in dictionary {dict_b!r}", **locals()) status = False 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 try: properties_list = self._metadata_container.entities[ api_resource.odata_type].properties.values() try: 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 get_resource(self, url, discovery_container, acceptable_return_codes=None, api_endpoint_override=None, check_resource_against_metadata=None): """ Sends http GET request to remote endpoint and retrieves resource odata_id. :type url: str :type discovery_container: DiscoveryContainer :type acceptable_return_codes: list(int) :rtype link :rtype status :rtype status_code :rtype response_body :rtype headers """ link, status, status_code, response_body, headers = \ self._perform_call(url, acceptable_return_codes=acceptable_return_codes, api_endpoint_override=api_endpoint_override) if status == RequestStatus.SUCCESS: if response_body and status_code != ReturnCodes.NOT_FOUND: status = discovery_container.add_resource( ApiResource(link.link, link.netloc, response_body, discovery_container.get_expected_odata_type_for_url(link.link)), check_resource_against_metadata=check_resource_against_metadata) else: cts_error("{url:id} Get failed. Status code: {code}", url=url, code=status_code) return link, status, status_code, response_body, headers
def get_resource(self, url, discovery_container, acceptable_return_codes=None, api_endpoint_override=None, check_resource_against_metadata=None): """ Sends http GET request to remote endpoint and retrieves resource odata_id. :type url: str :type discovery_container: DiscoveryContainer :type acceptable_return_codes: list(int) :rtype link :rtype status :rtype status_code :rtype response_body :rtype headers """ link, status, status_code, response_body, headers = \ self._perform_call(url, acceptable_return_codes=acceptable_return_codes, api_endpoint_override=api_endpoint_override) if not discovery_container: return link, status, status_code, response_body, headers if status == RequestStatus.SUCCESS: if response_body and status_code != ReturnCodes.NOT_FOUND: status = discovery_container.add_resource( ApiResource(link.link, link.netloc, response_body, discovery_container.get_expected_odata_type_for_url(link.link)), check_resource_against_metadata=check_resource_against_metadata) else: cts_error("{url:id} Get failed. Status code: {code}", url=url, code=status_code) return link, status, status_code, response_body, headers
def _process_local_schema_file(self, file_uri): try: full_uri = os.path.join(self.metadata_dir, file_uri) with open(full_uri) as f: self.read_metadata_from_strings(file_uri, f.read()) except IOError: cts_error("Metadata file {file} not found", file=full_uri)
def process_action(self, configuration): replay_id = configuration.replay_id[0] print "Using CTS in version %s to replay execution %s" \ % (ColorPrinter.format_text(BuildInformation.BUILD_VERSION, bold=True), replay_id) error, script_execution_id = split_replay_id(replay_id) if error: return # TODO: warn user when he tries to replay using newer CTS script_execution = ScriptDAO.get_script_execution_details( script_execution_id) if script_execution is None: cts_error( "Recording for script execution id={id:ignore} not found", id=script_execution_id) return script_path = script_execution.script_path configuration = self._configuration_from_string( script_execution.configuration) test_plan = self._prepare_test_plan(script_path) environ[ReplayController.CTS_REPLAY_SCRIPT_EXECUTION_ID] = str( script_execution_id) self._execute(configuration, test_plan)
def _test_create_volume(self): print "TEST_CASE::Create volume" post_volume_payload = dict( CapacityBytes=10000000, CapacitySources=[], AccessCapabilities=[] ) status, status_code, response_body, headers = self.api_caller.post_resource( self.chosen_volume_collection, self.discovery_container, payload=post_volume_payload, acceptable_return_codes=[ReturnCodes.OK, ReturnCodes.ACCEPTED, ReturnCodes.CREATED] ) if not status: cts_error("CTS cannot create volume") self.set_status_failed() return ValidationStatus.FAILED self.created_volume = headers['location'] cts_message("Volume created at {location}".format(location=self.created_volume)) if not self._verify_size_of_created_volume(self.created_volume, post_volume_payload): cts_error("Service create volume with wrong capacity") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED
def _test_create_volume(self): print "TEST_CASE::Create volume" post_volume_payload = dict(CapacityBytes=10000000) status, status_code, response_body, headers = self.api_caller.post_resource( self.chosen_volume_collection, self.discovery_container, payload=post_volume_payload, acceptable_return_codes=[ ReturnCodes.OK, ReturnCodes.ACCEPTED, ReturnCodes.CREATED ]) if not status: cts_error("CTS cannot create volume") self.set_status_failed() return ValidationStatus.FAILED self.created_volume = headers['location'] cts_message("Volume created at {location}".format( location=self.created_volume)) if not self._verify_size_of_created_volume(self.created_volume, post_volume_payload): cts_error("Service create volume with wrong capacity") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED
def validate(self, resource, resource_path, annotations=None): """ :type annotations: dict :type resource: dict :type resource_path: str :rtype: str """ try: odata_type = self.metadata_container.map_type(get_odata_type(resource)) 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 except KeyError: pass #this is legal that complex type does not have odata.type specified types_consistency_validation_status, complex_type_name = self.validate_types_consistency(resource, resource_path) if self.metadata_container.to_be_ignored(complex_type_name): return ValidationStatus.PASSED if complex_type_name == self.name: resource_validation_status = self.validate_properties(resource, resource_path) else: try: resource_validation_status = self.metadata_container.types[complex_type_name].validate(resource, resource_path) except KeyError: resource_validation_status = ValidationStatus.FAILED except Exception as err: import traceback cts_error("{path} - exception {err:exceptions}: {stack:stacktrace}", path=resource_path, err=err, stack=traceback.format_exc()) resource_validation_status = ValidationStatus.FAILED return ValidationStatus.join_statuses(types_consistency_validation_status, resource_validation_status)
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 _test_case_create_logical_drive(self, logical_drive_collection, payload=None): print "TEST_CASE::Create a logical drive" if not payload: payload = self.post_logical_drive_payload status, status_code, response_body, headers = self.api_caller.post_resource( logical_drive_collection, self.discovery_container, payload=payload) if not status: cts_error("Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return ValidationStatus.FAILED try: self.created_logical_drive = headers["Location"] self.created_logical_drive_netloc = \ self.discovery_container.get_netloc(logical_drive_collection) print "MESSAGE::Newly created logical drive url %s" % self.created_logical_drive except KeyError: cts_error("In header shall be provided location to created resource") self.set_status_failed() return ValidationStatus.FAILED print "MESSAGE::Logical drive created" self.set_status_passed() return ValidationStatus.PASSED
def _do_request(self, kwargs, url, http_method): if ReplayController.replay_mode_on(): response = ReplayController.request(http_method, url, **kwargs) else: response = None if response is None: requests_method = { HttpMethods.GET: requests.get, HttpMethods.PATCH: requests.patch, HttpMethods.POST: requests.post, HttpMethods.DELETE: requests.delete }[http_method] try: self._suppress_urllib3_error() response = requests_method(url, **kwargs) except OpenSSL.SSL.Error as ose: cts_error("There is a problem with CertFile or KeyFile: {err}", err=' '.join(ose.message[0])) except requests.RequestException as err: cts_error("{method} {url:id} Error {err:exception}", method=http_method, url=url, err=err) return None return response
def do_patch(self, payload): payload_shifted = create_data(self.property_full_path, payload) status, status_code, response_body, headers = self._api_caller.patch_resource( self.api_resource.url, self.discovery_container, payload=payload_shifted, acceptable_return_codes=self.patching_strategy. allowable_return_codes) patch_applied = self.patching_strategy.was_patch_applied(status_code) if status != RequestStatus.SUCCESS: cts_error( "{odata_id:id} Patching {property} failed (unexpected status code: {code})", odata_id=self.api_resource.odata_id, property=self.property_description.name, code=status_code) validation_status = ValidationStatus.FAILED elif not patch_applied: cts_error( "{odata_id:id} Patching {property} failed (patch not applied); status code: {code}", odata_id=self.api_resource.odata_id, property=self.property_description.name, code=status_code) validation_status = ValidationStatus.FAILED else: validation_status = ValidationStatus.PASSED return validation_status
def _process_property(self, property_description, json_body, url, path): if not json_body: return if property_description.type in self._metadata_container.entities.keys( ): return self._process_entity(url, property_description, json_body, path=path.append( property_description.name)) elif property_description.type in self._metadata_container.types.keys( ): if self._metadata_container.types[ property_description. type].type_category == MetadataTypeCategories.COMPLEX_TYPE: return self._process_complex_type( property_description, json_body, url, path=path.append(property_description.name)) else: cts_error("{url:id}#{path} : Unknown type {type}", url=url, path=path, type=property_description.type)
def _test_case_bind_acl_to_port(self, port): print "TEST_CASE::Bind ACL to a port" port_id = dict() port_id["@odata.id"] = port payload = dict(Port=port_id) status, status_code, response_body, _ = \ self.api_caller.post_resource(self.created_acl + "/Actions/EthernetSwitchACL.Bind", self.discovery_container, payload=payload, acceptable_return_codes= [ReturnCodes.NO_CONTENT], expect_location=False, api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of POST response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::ACL successfully bound to port" self.set_status_passed() return True
def _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 _test_case_delete_rules_from_acl(self): print "TEST_CASE::Delete the created rules from the ACL" for rule_type, rule_uri in self.created_rules.iteritems(): print "MESSAGE::Deleting {type}".format(type=rule_type) status, status_code, response_body, _ = self.api_caller.delete_resource( rule_uri, self.discovery_container) if not status: cts_error( "Wrong status code of DELETE response: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::Checking if the rule was actually deleted" link, status, status_code, response_body, _ = \ self.api_caller.get_resource(rule_uri, self.discovery_container, acceptable_return_codes=[ ReturnCodes.NOT_FOUND], api_endpoint_override=self.created_acl_netloc) if not status: cts_error( "Wrong status code of GET response after deletion: {status_code}; response body: {response_body:response_body}", **locals()) self.set_status_failed() return False print "MESSAGE::The rule was not found (as intended)" print "MESSAGE::ACL Rules deleted succesfully" self.set_status_passed() return True
def read_metadata_for_services(self, *services): """ :type services: list[string] """ digest = DirDigest(self._metadata_home(), '.xml', DirDigest.LABEL_METADATA) if not digest.is_valid() and not getenv('CTS_SKIP', None): cts_error( "Metadata located in {dir} is corrupted or has been tampered. Expected: {expected}, " "Is: {current}", dir=self._metadata_home(), expected=digest.official_digest, current=digest.digest) cts_message("{count} xml files have been found in {dir}".format( count=len(digest), dir=self._metadata_home())) digest.report_differences() for service in services: try: self.metadata_dir = os.path.join( self._metadata_home(), MetadataManager.SERVICE_TO_DIR[service]) self.read_metadata_from_dir(self.metadata_dir) except KeyError: cts_error( "Internal error. Unknown metadata for service {service}", service=service) return False return True
def _validate_additional_property(self, property_name, property_value, resource_path, unused_properties): if self.allow_additional_properties: print "DEBUG::Additional property {resource_path}->{property_name}".format( resource_path=resource_path, property_name=property_name) try_suggest_other_property_names(name=property_name, candidates=unused_properties) odata_type = match_dynamic_property(property_name, self) if odata_type is None: try: odata_type = get_odata_type(property_value) except KeyError: if isinstance(property_value, dict): cts_error("Property {path}->{property_name} is of complex type without @odata_type", path=resource_path, property_name=property_name) return ValidationStatus.FAILED else: return ValidationStatus.PASSED mapped_odata_type = self.metadata_container.map_type(odata_type) # assuming all aditional properties are nullable if property_value is None: return ValidationStatus.PASSED if not self.metadata_container.to_be_ignored(odata_type, mapped_odata_type): return self.metadata_container.types[odata_type].validate( property_value, resource_path + '->' + property_name) else: return ValidationStatus.PASSED else: cts_error("Property {resource_path}->{property_name} is present, but not defined in metadata. " \ "Type {type_name} does not allow additional properties", resource_path=resource_path, property_name=property_name, type_name=self.name) try_suggest_other_property_names(name=property_name, candidates=unused_properties) return ValidationStatus.FAILED
def __find_volumes_without_any_links(self, optional_pre_check=False): reusable_volumes = [] logical_drive_collections = dict(self.discovery_container.get_resources( MetadataConstants.VOLUME_COLLECTION, any_child_version=True)) # Allocated Volumes are connected with this same type as Volume volumes_collection_wo_allocated_volumes = [x for x in logical_drive_collections if not "AllocatedVolumes" in x] for collection in volumes_collection_wo_allocated_volumes: if not self.chosen_volume_collection: self.chosen_volume_collection = collection logical_drive_groups = dict(self.discovery_container.get_resources(MetadataConstants.VOLUME, any_child_version=True)) volumes_collection = [] for c in logical_drive_groups.keys(): try: # this Volume we can use to our tests if len(self.discovery_container.get(c).body["Links"]["Oem"]["Intel_RackScale"]["Endpoints"]) == 0: volumes_collection.append(c) except KeyError: pass # if there are any free Volume, suspend test if len(volumes_collection) == 0: if optional_pre_check: return False cts_error("No reusable Volume. Create a Volume without linked Endpoints") self.set_status_blocked() else: reusable_volumes.extend(volumes_collection) return reusable_volumes
def get_property_value(self): try: value = self.api_resource.get_value_from_path(self.property_full_path) except KeyError as key: cts_error("Unable to access {odataid:id}->{path}. Key={key}", odataid=self.api_resource.odata_id, path="->".join(self.variable_path), key=key) raise return value
def __delete_location(self, location_url): status, _, _, _ = self.api_caller.delete_resource(location_url, self.discovery_container, acceptable_return_codes=[ReturnCodes.NO_CONTENT]) if not status: cts_error("Resource at %s was not properly deleted" % location_url) return False return self.__verify_resource_non_exist(location_url)
def test_with_regular_formatting_fields(self): with StdoutCapture() as output: cts_error('kot{kogo}wazy{ile}', kogo='prezesa', ile='sporo') self.assertIsNotNone(re.search("kotprezesawazysporo.*\[#Id=[0-9a-f]{32}\]", output.raw)) with StdoutCapture() as output: cts_error('kot{kogo}wazy{ile}kg', kogo='prezesa', ile=20) self.assertIsNotNone(re.search("kotprezesawazy20kg.*\[#Id=[0-9a-f]{32}\]", output.raw))
def _load_metadata(self, metadata_ref): for loader in LOADER_CHAIN: metadata_container = loader().load(metadata_ref, self.qualifiers) if metadata_container is not None: return metadata_container msg = "Unable to load metadata: {ref}".format(ref=metadata_ref) cts_error(msg) sys.stderr.write(msg + "\nExiting...") exit(1)
def main(): try: from cts_framework.actions.actions_dispatcher import ActionsDispatcher actions_dispatcher = ActionsDispatcher() actions_dispatcher.process_application() except ImportError as err: import traceback cts_error("Dependencies are not met: {err:exception}; stacktrace={stack:stacktrace}", err=err, stack=traceback.format_exc()) exit(1)
def __verify_resource_non_exist(self, location_url_verification): status, _, _, _, _ = self.api_caller.get_resource(location_url_verification, self.discovery_container, acceptable_return_codes=[ReturnCodes.BAD_REQUEST, ReturnCodes.NOT_FOUND]) if not status: cts_error("Resource at %s was not properly deleted" % location_url_verification) return False cts_message("Resource at %s was properly deleted" % location_url_verification) return True
def _dereference(self): obj = self.ref() if obj is None: try: obj = self.discovery_container[self.url] self.ref = weakref.ref(obj) except KeyError: cts_error('ApiResources {url} is no longer valid', url=self.url) return None return obj
def _get_proper_odata_type_versions(self, odata_type): if not self.metadata_container: return [odata_type] if odata_type not in self.metadata_container.entities: cts_error("Asked for resources of type {odata_type} but the type is not present in metadata", odata_type=odata_type) return [] return [key for key in self._dataframes.keys() if self.metadata_container.entities[odata_type].is_compatible(key)]
def load(self, filepath=None): if filepath is None: filepath = self._filepath with open(filepath, 'r') as json_data: try: return json.load(json_data) except ValueError as e: cts_error('Can not load Use Cases: %s' % e) exit(1)
def __getitem__(self, item): """ :rtype: cts_core.metadata.model.entity.Entity """ mapped_type = self.type_mapping_func(item) try: return dict.__getitem__(self, mapped_type) except KeyError as key: if item is not None: cts_error("Unknown entity type {key}", **locals()) raise KeyError(key)
def __getitem__(self, item): """ :rtype: EnumType or ComplexType or TypeDefinition or PrimitiveType """ mapped_type = self.type_mapping_func(item) try: return dict.__getitem__(self, mapped_type) except KeyError as key: if item is not None: cts_error("Unknown type {key}", **locals()) raise KeyError(key)
def sanitize_configuration(cls, can_run, configuration): test_case_configuration = Configuration() for parameter in cls.CONFIGURATION_PARAMETERS: configuration_param_value = getattr(configuration, parameter.name) if configuration_param_value is None and parameter.is_required: cts_error("Required configuration parameter {name} not defined", name=parameter.name) can_run = False if configuration_param_value is None and parameter.default_value is not None: configuration_param_value = parameter.default_value test_case_configuration.update_configuration(**{parameter.name: configuration_param_value}) return can_run, test_case_configuration
def odataid_lookup(self, *odata_id): ids = [re.sub(r'#.*$', '', str(id)) for id in odata_id] resources = [ApiResourceProxy(r, self) for r in self.itervalues() if r.odata_id in ids] if len(resources) < len(odata_id): found_ids = set([r.odata_id for r in resources]) requested_ids = set(ids) diff = requested_ids.difference(found_ids) if len(diff) > 0: cts_error('References to unknown resources: {refs}', refs=', '.join(diff)) return resources
def run_commands_on_host(commands, credentials): responses_from_ssh_target = [] with ExternalCommandOverSSH(**credentials) as host: for cmd in commands: _, stdout, stderr = host.exec_command(cmd) err = stderr.read() if err is not '': cts_error(err) responses_from_ssh_target.append(stdout.read()) return responses_from_ssh_target[-1]
def __verify_command_type(external_cmd): command_type, command_location = (None,) * 2 try: command_type = external_cmd[0] if command_type.lower() != 'python': cts_message('CTS support only Python external scripts') return False command_location = external_cmd[1] except IndexError: cts_error('Missing verification command details') return command_type, command_location
def validate(self, resource, resource_path, annotations=None): """ :type annotations: dict :type resource: * :type resource_path: str :rtype: str """ if resource != None: cts_error("Value of property {resource_path} is not null but {resource}", resource_path=resource_path, resource=resource) return ValidationStatus.FAILED else: return ValidationStatus.PASSED
def validate(self, resource, resource_path, annotations=None): """ :type annotations: dict :type resource: dict :type resource_path: str :rtype: str """ try: return self.metadata_container.types[self.base_type].validate(resource, resource_path, annotations) except KeyError: cts_error("Unknown base type {base_type} for type {type_definition} " + "during validation of resource {resource_path}", base_type=self.base_type, type_definition=self.name, resource_path=resource_path) return ValidationStatus.FAILED
def test_identical_error_id_for_different_stacktraces(self): with StdoutCapture() as output: cts_error('{stacktrace:stacktrace}', stacktrace='one') match = re.search("\[#Id=(?P<id>[0-9a-f]{32})\]", output.raw) self.assertIsNotNone(match) id_1 = match.group('id') with StdoutCapture() as output: cts_error('{stacktrace:stacktrace}', stacktrace='two') match = re.search("\[#Id=(?P<id>[0-9a-f]{32})\]", output.raw) self.assertIsNotNone(match) id_2 = match.group('id') self.assertEqual(id_1, id_2)
def _test_create_target_endpoint(self): print "TEST_CASE::Create target endpoint" selected_volume = self.discovery_container.get(self.chosen_volume) random_suffix = random.randrange(1, RANDOM_RANGE, 1) post_target_endpoint_payload = dict( Identifiers=[dict( DurableName="iqn.initiator" + str(random_suffix), DurableNameFormat="iQN" )], ConnectedEntities=[dict( EntityRole="Target", EntityLink={"@odata.id": selected_volume.odata_id}, )], IPTransportDetails=[], Oem=dict( Intel_RackScale=dict( Authentication=dict( Username="******" + str(random_suffix), Password="******" )))) status, status_code, response, headers = self.api_caller.post_resource( self.chosen_endpoint, self.discovery_container, payload=post_target_endpoint_payload, acceptable_return_codes=[ReturnCodes.OK, ReturnCodes.ACCEPTED, ReturnCodes.CREATED]) if not status: cts_error("CTS can not create Target Endpoint") self.set_status_failed() return ValidationStatus.FAILED self.target_endpoint = headers['location'] cts_message("Target Endpoint created at {location}".format(location=self.target_endpoint)) try: verify = self.__verify_created_target_endpoint(self.target_endpoint, post_target_endpoint_payload) except: verify = False if not verify: cts_error("Service create target endpoint with wrong value") self.set_status_failed() return ValidationStatus.FAILED self.set_status_passed() return ValidationStatus.PASSED