Example #1
0
 def send_and_parse(request_data):
     """ Gets the token, sends the requst, performs async wait, parses response, returns status code """
     rendered_data = self.get_request_data_with_token(
         request_data.rendered_data)
     response = request_utilities.send_request_data(rendered_data)
     response_to_parse, _, _ = async_request_utilities.try_async_poll(
         rendered_data, response, request_data.max_async_wait_time)
     if request_data.parser:
         request_utilities.call_response_parser(request_data.parser,
                                                response_to_parse)
     return response.status_code
Example #2
0
def try_parse_GET_request(request_data):
    """ Helper that creates a GET request from a request's rendered
    data, sends that request, and then checks for a success (200) status.

    @param request_data: The request to convert to a GET. This is fully
                         rendered request data in a format that is ready
                         to be sent to HttpSock's sendRecv function.
    @type  request_data: Str

    @return: The response of the GET request if it's a 200, otherwise None
    @rtype : Str

    """
    async_get_data = make_GET_request(request_data)
    get_response = request_utilities.send_request_data(async_get_data)
    if get_response.status_code == '200':
        # The new GET request was successful, set it as the data to parse and return
        return get_response
    return None
Example #3
0
def try_async_poll(request_data, response, max_async_wait_time):
    """ Helper that will poll the server until a certain resource
    becomes available.

    This function will check the response from a PUT or POST request to
    see if it contains one of the known async resource creations strings and,
    if so, will poll the URL specified in the response until the resource is
    said to be available or a max timeout has been reached.

    @param request_data: The request that was sent's data string
    @type  request_data: Str
    @param response: The response returned after @request was sent
    @type  response: Str
    @param max_async_wait_time: The maximum amount of time we will wait (in
                                seconds) for the resource to become available
    @type  max_async_wait_time: Int

    @return: A tuple containing:
             - The response for parsing, which will either be the @response
             passed to this function or the response from the GET request
             if it was an async request.
             - A boolean that is True if there was an error when creating the
             resource
             - A Boolean that tells the caller whether or not async polling was
             attempted
    @rtype : Tuple(HttpResponse, boolean, boolean)

    """
    from utils.logger import raw_network_logging as RAW_LOGGING
    from utils.logger import print_async_results as LOG_RESULTS

    response_to_parse = response
    resource_error = False
    async_waited = False
    if  Settings().wait_for_async_resource_creation and max_async_wait_time > 0\
    and (request_data.startswith("PUT") or request_data.startswith("POST")):
        # Get the request used for polling the resource availability
        data, data_in_poll_response = get_polling_request(response)
        if data:
            async_waited = True
            poll_wait_seconds = 1
            start_time = time.time()
            RAW_LOGGING("Waiting for resource to be available...")
            while (time.time() - start_time) < max_async_wait_time:
                try:
                    # Send the polling request
                    poll_response = request_utilities.send_request_data(data)
                    time_str = str(round((time.time() - start_time), 2))
                    if data_in_poll_response:
                        # If this returned a '200' status code, the response should contain the parsable data.
                        # Otherwise, continue to poll as the resource has not yet been created. These types will
                        # return a '202 - Accepted' while the resource is still being created.
                        if poll_response.status_code == '200':
                            LOG_RESULTS(
                                request_data,
                                f"Resource creation succeeded after {time_str} seconds."
                            )
                            # Break and return the polling response to be parsed
                            response_to_parse = poll_response
                            break
                    else:
                        # The data is not in the polling response and must be fetched separately.
                        # This comes from Azure-Async responses that do not contain a Location: field
                        # Check for the status of the resource
                        response_body = json.loads(poll_response.json_body)
                        done = str(response_body["status"]).lower()
                        if done == "succeeded":
                            LOG_RESULTS(
                                request_data,
                                f"Resource creation succeeded after {time_str} seconds."
                            )
                            get_response = try_parse_GET_request(request_data)
                            if get_response:
                                response_to_parse = get_response
                            break
                        elif done == "failed":
                            LOG_RESULTS(
                                request_data,
                                f"The server reported that the resource creation Failed after {time_str} seconds."
                            )
                            resource_error = True
                            break
                except json.JSONDecodeError as err:
                    LOG_RESULTS(
                        request_data,
                        "Failed to parse body of async response, retrying.")
                    # This may have been due to a connection failure. Retry until max_async_wait_time.
                    time.sleep(poll_wait_seconds)
                    continue
                except Exception as err:
                    LOG_RESULTS(
                        request_data,
                        f"An exception occurred while parsing the poll message: {err!s}"
                    )
                    break

                try:
                    if not poll_response.status_code.startswith('2'):
                        LOG_RESULTS(
                            request_data,
                            f"Resource creation failed after {time_str} seconds"
                            f" because status code '{poll_response.status_code}' was received."
                        )
                        break
                except Exception as err:
                    LOG_RESULTS(
                        request_data,
                        f"An exception occurred while parsing the poll message: {err!s}"
                    )
                    break
                time.sleep(poll_wait_seconds)
            else:
                RAW_LOGGING(
                    "Resource polling timed out before the resource was available."
                )
                LOG_RESULTS(
                    request_data,
                    f"Failed to create resource in {max_async_wait_time} seconds."
                )
                RAW_LOGGING("Attempting to get resources from GET request...")
                get_response = try_parse_GET_request(request_data)
                if get_response:
                    # Use response from the GET request for parsing. If the GET request failed,
                    # the caller will know to try and use the original PUT response for parsing
                    response_to_parse = get_response

    return response_to_parse, resource_error, async_waited
Example #4
0
    def render(self,
               candidate_values_pool,
               lock,
               preprocessing=False,
               postprocessing=False):
        """ Core routine that performs the rendering of restler sequences. In
        principal all requests of a sequence are being constantly rendered with
        a specific values combination @param request._current_combination_id
        which we know in the past led to a valid rendering and only the last
        request of the sequence is being rendered iteratively with all feasible
        value combinations. Each time a "valid rendering" is found for the last
        request of the sequence (where "valid rendering" is defined according
        to "VALID_CODES"), the routine returns a new sequence which has an
        end-to-end (i.e., all requests) "valid rendering" and can be added in
        the sequences collection in order to be used in the future as a building
        block for longer sequences.


        @param candidate_values_pool: The pool of values for primitive types.
        @type candidate_values_pool: Dict
        @param lock: Lock object used for sync of more than one fuzzing jobs.
        @type  lock: thread.Lock object
        @param preprocessing: Set to true if rendering during preprocessing
        @type  preprocessing: Bool

        @return: A RenderedSequence object containing the sequence, the final
                 request's response, whether or not the final request received
                 a valid status code, and a FailureInformation enum if there was
                 a failure or bug detected during rendering.
        @rtype : RenderedSequence
        """
        # Try rendering  all primitive type value combinations for last request
        request = self.last_request

        # for clarity reasons, don't log requests whose render iterator is over
        if request._current_combination_id <\
                request.num_combinations(candidate_values_pool):
            CUSTOM_LOGGING(self, candidate_values_pool)

        self._sent_request_data_list = []
        for rendered_data, parser in\
                request.render_iter(candidate_values_pool,
                                    skip=request._current_combination_id,
                                    preprocessing=preprocessing):
            # Hold the lock (because other workers may be rendering the same
            # request) and check whether the current rendering is known from the
            # past to lead to invalid status codes. If so, skip the current
            # rendering.
            if lock is not None:
                lock.acquire()
            should_skip = Monitor().is_invalid_rendering(request)
            if lock is not None:
                lock.release()

            # Skip the loop and don't forget to increase the counter.
            if should_skip:
                RAW_LOGGING("Skipping rendering: {}".\
                            format(request._current_combination_id))
                request._current_combination_id += 1
                continue

            # Clean up internal state
            self.status_codes = []
            dependencies.reset_tlb()

            sequence_failed = False
            # Step A: Static template rendering
            # Render last known valid combination of primitive type values
            # for every request until the last
            for i in range(len(self.requests) - 1):
                prev_request = self.requests[i]
                prev_rendered_data, prev_parser =\
                    prev_request.render_current(candidate_values_pool,
                    preprocessing=preprocessing)

                # substitute reference placeholders with resolved values
                if not Settings().ignore_dependencies:
                    prev_rendered_data =\
                        self.resolve_dependencies(prev_rendered_data)

                prev_req_async_wait = Settings(
                ).get_max_async_resource_creation_time(prev_request.request_id)
                prev_producer_timing_delay = Settings(
                ).get_producer_timing_delay(prev_request.request_id)

                prev_response = request_utilities.send_request_data(
                    prev_rendered_data)
                prev_response_to_parse, resource_error, async_waited = async_request_utilities.try_async_poll(
                    prev_rendered_data, prev_response, prev_req_async_wait)
                prev_parser_threw_exception = False
                # Response may not exist if there was an error sending the request or a timeout
                if prev_parser and prev_response_to_parse:
                    prev_parser_threw_exception = not request_utilities.call_response_parser(
                        prev_parser, prev_response_to_parse, prev_request)
                prev_status_code = prev_response.status_code

                # If the async logic waited for the resource, this wait already included the required
                # producer timing delay. Here, set the producer timing delay to zero, so this wait is
                # skipped both below for this request and during replay
                if async_waited:
                    prev_producer_timing_delay = 0
                else:
                    prev_req_async_wait = 0

                self.append_data_to_sent_list(prev_rendered_data, prev_parser,
                                              prev_response,
                                              prev_producer_timing_delay,
                                              prev_req_async_wait)

                if not prev_status_code:
                    logger.write_to_main(
                        f"Error: Failed to get status code during valid sequence re-rendering.\n"
                    )
                    sequence_failed = True
                    break

                if prev_response.has_bug_code():
                    BugBuckets.Instance().update_bug_buckets(self,
                                                             prev_status_code,
                                                             reproduce=False,
                                                             lock=lock)
                    sequence_failed = True
                    break

                if prev_parser_threw_exception:
                    logger.write_to_main(
                        "Error: Parser exception occurred during valid sequence re-rendering.\n"
                    )
                    sequence_failed = True
                    break

                if resource_error:
                    logger.write_to_main(
                        "Error: The resource was left in a Failed state after creation during valid sequence re-rendering.\n"
                    )
                    sequence_failed = True
                    break

                # If the previous request is a resource generator and we did not perform an async resource
                # creation wait, then wait for the specified duration in order for the backend to have a
                # chance to create the resource.
                if prev_producer_timing_delay > 0 and prev_request.is_resource_generator(
                ):
                    print(
                        f"Pausing for {prev_producer_timing_delay} seconds, request is a generator..."
                    )
                    time.sleep(prev_producer_timing_delay)

                # register latest client/server interaction
                timestamp_micro = int(time.time() * 10**6)
                self.status_codes.append(
                    status_codes_monitor.RequestExecutionStatus(
                        timestamp_micro,
                        prev_request.hex_definition, prev_status_code,
                        prev_response.has_valid_code(), False))

            if sequence_failed:
                self.status_codes.append(
                    status_codes_monitor.RequestExecutionStatus(
                        int(time.time() * 10**6), request.hex_definition,
                        RESTLER_INVALID_CODE, False, True))
                Monitor().update_status_codes_monitor(self, self.status_codes,
                                                      lock)
                return RenderedSequence(
                    failure_info=FailureInformation.SEQUENCE)

            # Step B: Dynamic template rendering
            # substitute reference placeholders with ressoved values
            # for the last request
            if not Settings().ignore_dependencies:
                rendered_data = self.resolve_dependencies(rendered_data)

            # Render candidate value combinations seeking for valid error codes
            request._current_combination_id += 1

            req_async_wait = Settings().get_max_async_resource_creation_time(
                request.request_id)

            response = request_utilities.send_request_data(rendered_data)
            response_to_parse, resource_error, _ = async_request_utilities.try_async_poll(
                rendered_data, response, req_async_wait)
            parser_exception_occurred = False
            # Response may not exist if there was an error sending the request or a timeout
            if parser and response_to_parse:
                parser_exception_occurred = not request_utilities.call_response_parser(
                    parser, response_to_parse, request)
            status_code = response.status_code
            if not status_code:
                return RenderedSequence(None)

            self.append_data_to_sent_list(rendered_data,
                                          parser,
                                          response,
                                          max_async_wait_time=req_async_wait)

            rendering_is_valid = not parser_exception_occurred\
                and not resource_error\
                and response.has_valid_code()
            # register latest client/server interaction and add to the status codes list
            response_datetime = datetime.datetime.now(datetime.timezone.utc)
            timestamp_micro = int(response_datetime.timestamp() * 10**6)

            self.status_codes.append(
                status_codes_monitor.RequestExecutionStatus(
                    timestamp_micro, request.hex_definition, status_code,
                    rendering_is_valid, False))

            # add sequence's error codes to bug buckets.
            if response.has_bug_code():
                BugBuckets.Instance().update_bug_buckets(self,
                                                         status_code,
                                                         lock=lock)

            Monitor().update_status_codes_monitor(self, self.status_codes,
                                                  lock)

            # Register current rendering's status.
            if lock is not None:
                lock.acquire()
            Monitor().update_renderings_monitor(request, rendering_is_valid)
            if lock is not None:
                lock.release()

            if Monitor().remaining_time_budget <= 0 and not postprocessing:
                raise TimeOutException("Exceeded Timeout")

            if lock is not None:
                lock.acquire()
            # Deep  copying here will try copying anything the class has access
            # to including the shared client monitor, which we update in the
            # above code block holding the lock, but then we release the
            # lock and one thread can be updating while another is copying.
            # This is a typlical nasty read after write syncronization bug.
            duplicate = copy.deepcopy(self)
            if lock is not None:
                lock.release()

            datetime_format = "%Y-%m-%d %H:%M:%S"
            # return a rendered clone if response indicates a valid status code
            if rendering_is_valid or Settings().ignore_feedback:
                return RenderedSequence(
                    duplicate,
                    valid=True,
                    final_request_response=response,
                    response_datetime=response_datetime.strftime(
                        datetime_format))
            else:
                information = None
                if response.has_valid_code():
                    if parser_exception_occurred:
                        information = FailureInformation.PARSER
                    elif resource_error:
                        information = FailureInformation.RESOURCE_CREATION
                elif response.has_bug_code():
                    information = FailureInformation.BUG
                return RenderedSequence(
                    duplicate,
                    valid=False,
                    failure_info=information,
                    final_request_response=response,
                    response_datetime=response_datetime.strftime(
                        datetime_format))

        return RenderedSequence(None)