Ejemplo n.º 1
0
    def retrieve_response(self) -> requests.Response:
        """
        Returns a final requests.Response object from cruise-control
        where Response.text is JSON-formatted.
        :return: requests.Response
        """
        # cruise-control's JSON response has a 'progress' key in it so long
        # as the response is not final.
        #
        # Once the response is final, it does not contain the 'progress' key.
        #
        # Accordingly, keep getting the response from this session and checking
        # it for the 'progress' key.
        #
        # Return the response as JSON once we get a valid JSON response that we
        # think is 'final'.

        # Alert the humans to long-running poll
        if self.print_to_stdout_enabled:
            print_error("Starting long-running poll of {}".format(self.url))

        # TODO: Session.get and Session.post can return a plethora of exceptions;
        # they should be handled here.
        response = self.session_http_method(self.url, headers=self.headers)
        while 'progress' in response.json().keys():
            if self.print_to_stdout_enabled:
                display_response(response)
            response = self.session_http_method(self.url, headers=self.headers)

        # return the requests.response object
        return response
Ejemplo n.º 2
0
def main():
    # Instantiate a convenience class to pass around information about available endpoints and parameters.
    e = ExecutionContext()

    # Display and parse command-line arguments for interacting with cruise-control
    parser = build_argument_parser(e)
    args = parser.parse_args()

    # Get the endpoint that the parsed args specify
    endpoint = get_endpoint(args=args, execution_context=e)

    # Get the socket address for the cruise-control we're communicating with
    cc_socket_address = args.socket_address

    # Retrieve the response and display it
    json_responder = CruiseControlResponder()
    response = json_responder.retrieve_response_from_Endpoint(cc_socket_address, endpoint)
    display_response(response)
Ejemplo n.º 3
0
def main():
    # Instantiate a convenience class to pass around information about available endpoints and parameters.
    e = ExecutionContext()

    # Display and parse command-line arguments for interacting with cruise-control
    parser = build_argument_parser(e)
    args = parser.parse_args()

    # Get the endpoint that the parsed args specify
    endpoint = get_endpoint(args=args, execution_context=e)

    # Get the socket address for the cruise-control we're communicating with
    cc_socket_address = args.socket_address

    # Generate the correct URL from the endpoint and the socket address
    url = generate_url_from_cc_socket_address(
        cc_socket_address=cc_socket_address, endpoint=endpoint)

    # Get a responder from the given URL and endpoint
    json_responder = get_responder(endpoint=endpoint, url=url)

    # Retrieve the response and display it
    response = json_responder.retrieve_response()
    display_response(response)
Ejemplo n.º 4
0
def query_cruise_control(args):
    """
    Handles asking cruise-control for a requests.Response object, and pretty-
    printing the Response.text.

    :param args:
    :return:
    """
    # Deepcopy the args so that we can delete keys from it as we handle them.
    #
    # Otherwise successive iterations will step on each other's toes.
    arg_dict = vars(args)

    # If we have a broker list, we need to make it into a comma-separated list
    # and pass it to the Endpoint at instantiation.
    if 'brokers' in arg_dict:
        comma_broker_id_list = ",".join(args.brokers)
        endpoint: Endpoint.AbstractEndpoint = dest_to_Endpoint[
            args.endpoint_subparser](comma_broker_id_list)
        # Prevent trying to add this parameter a second time.
        del arg_dict['brokers']

    # Otherwise we can directly instantiate the Endpoint
    else:
        endpoint: Endpoint.AbstractEndpoint = dest_to_Endpoint[
            args.endpoint_subparser]()

    # Iterate only over the parameter flags; warn user if conflicts exist
    for flag in arg_dict:
        if flag in non_parameter_flags:
            pass
        else:
            # Presume None is ternary for ignore
            if arg_dict[flag] is not None:
                param_name = flag_to_parameter_name[flag]
                # Check for conflicts in this endpoint's parameter-space,
                # which here probably means that the user is specifying more
                # than one irresolvable flag.
                #
                # For the StateEndpoint only, we don't care if we overwrite it.
                # This is because we presume 'substates:executor' at instantiation.
                if endpoint.has_param(param_name) and not isinstance(
                        endpoint, Endpoint.StateEndpoint):
                    existing_value = endpoint.get_value(param_name)
                    raise ValueError(
                        f"Parameter {param_name}={existing_value} already exists in this endpoint.\n"
                        f"Unclear whether it's safe to remap to {param_name}={arg_dict[flag]}"
                    )
                else:
                    # If we have a destination broker list, we need to make it into a comma-separated list
                    if flag == 'destination_broker':
                        comma_broker_id_list = ",".join(arg_dict[flag])
                        endpoint.add_param(param_name, comma_broker_id_list)
                    else:
                        endpoint.add_param(param_name, arg_dict[flag])

    # We added this parameter already; don't attempt to add it again
    if 'destination_broker' in arg_dict:
        del arg_dict['destination_broker']

    # Handle hacking in json=true, if the user hasn't specified
    #
    # This is because it is easier to know when a JSON response is final,
    # compared to a text response.
    #
    # In fact, because it is not possible programmatically to know when a
    # text response is final, Responder actually does not support text responses.
    if not endpoint.has_param('json'):
        endpoint.add_param('json', 'true')

    # Handle add-parameter and remove-parameter flags
    #
    # Handle deconflicting adding and removing parameters, but don't
    # warn the user if they're overwriting an existing flag, since
    # these flags are meant as an admin-mode workaround to well-meaning defaults
    adding_parameter = 'add_parameter' in arg_dict and arg_dict['add_parameter']
    if adding_parameter:
        # Build a dictionary of parameters to add
        parameters_to_add = {}

        for item in arg_dict['add_parameter']:
            # Check that parameter contains an =
            if '=' not in item:
                raise ValueError("Expected \"=\" in the given parameter")

            # Check that the parameter=value string is correctly formatted
            split_item = item.split("=")
            if len(split_item) != 2:
                raise ValueError(
                    "Expected only one \"=\" in the given parameter")
            if not split_item[0]:
                raise ValueError("Expected parameter preceding \"=\"")
            if not split_item[1]:
                raise ValueError(
                    "Expected value after \"=\" in the given parameter")

            # If we are here, split_item is a correctly-formatted list of 2 items
            parameter, value = split_item
            # Add it to our running dictionary
            parameters_to_add[parameter] = value

    # The 'remove_parameter' string may not be in our namespace, and even if it
    # is, there may be no parameters supplied to it.
    #
    # Accordingly, check both conditions and store as a simpler boolean.
    removing_parameter = 'remove_parameter' in arg_dict and arg_dict[
        'remove_parameter']
    if removing_parameter:
        # Build a set of parameters to remove
        parameters_to_remove = set()
        for item in arg_dict['remove_parameter']:
            parameters_to_remove.add(item)

    # Validate that we didn't receive ambiguous input
    if adding_parameter and removing_parameter:
        if set(parameters_to_add) & parameters_to_remove:
            raise ValueError(
                "Parameter present in --add-parameter and in --remove-parameter; unclear how to proceed"
            )

    # Having validated parameters, now actually add or remove them.
    #
    # Do this without checking for conflicts from existing parameter=value mappings,
    # since we presume that if the user supplied these, they really want them
    # to override existing parameter=value mappings
    if adding_parameter:
        for parameter, value in parameters_to_add.items():
            endpoint.add_param(parameter, value)
    if removing_parameter:
        for parameter in parameters_to_remove:
            endpoint.remove_param(parameter)

    # Handle instantiating the correct URL for the Responder
    url = generate_url_from_cc_socket_address(args.socket_address, endpoint)

    # Handle instantiating the correct Responder
    if endpoint.http_method == "GET":
        json_responder = JSONDisplayingResponderGet(url)
    elif endpoint.http_method == "POST":
        json_responder = JSONDisplayingResponderPost(url)
    else:
        raise ValueError("Unexpected http_method {} in endpoint".format(
            endpoint.http_method))

    response = json_responder.retrieve_response()
    display_response(response)
Ejemplo n.º 5
0
    def retrieve_response(self, method, url, **kwargs) -> requests.Response:
        """
        Returns a final requests.Response object from cruise-control
        where Response.text is JSON-formatted.

        :return: requests.Response
        """
        # Alert the humans that long-running request is starting
        if 'params' in kwargs:
            url_with_params = f"{url}?{urlencode(kwargs['params'])}"
        else:
            url_with_params = url
        print_error(f"Starting long-running poll of {url_with_params}")
        for key, value in kwargs.items():
            if key == 'params':
                continue
            else:
                print_error(f"{key}: {value}")

        # Convenience closure to not have to copy-paste the parameters from
        # this current environment.
        def inner_request_helper():
            return self.request(method, url, **kwargs)

        def is_response_final(response: requests.Response):
            # Define an inner convenience closure to avoid
            # repeating ourselves
            def json_or_text_guesser():
                try:
                    # Try to guess whether the JSON response is final
                    return "progress" not in response.json().keys()

                except ValueError:
                    # We have a non-JSON (probably plain text) response,
                    # and a non-202 status code.
                    #
                    # This response may not be final, but we have no
                    # way of doing further guessing, so warn the humans
                    # as best as we can, then presume finality.
                    cc_version = response.headers.get('Cruise-Control-Version')
                    if cc_version is not None:
                        warnings.warn(
                            f"json=False received from cruise-control version ({cc_version}) "
                            f"that does not support 202 response codes. "
                            f"Please upgrade cruise-control to >=2.0.61, or "
                            f"use json=True with cruise-control-client. "
                            f"Returning a potentially non-final response.")
                    # No cc_version in the response headers
                    else:
                        # cruise-control won't return version information if
                        # servlet receives too-large-URI request
                        if response.status_code == 414:
                            pass
                        else:
                            warnings.warn(
                                "Unable to determine cruise-control version. "
                                "Returning a potentially non-final response.")
                    return True

            # We're talking to a version of cruise-control that supports
            # 202: accepted, and we know that this response is not final.
            if response.status_code == 202:
                return False
            else:
                # Guess about whether this version of cruise-control supports 202
                if "Cruise-Control-Version" in response.headers:
                    integer_semver = lambda x: [
                        int(elem) for elem in x.split('.')
                    ]
                    cc_version = integer_semver(
                        response.headers["Cruise-Control-Version"])
                    # 202 is supported and was not returned; response final
                    if cc_version >= [2, 0, 61]:
                        return True
                    # 202 is not supported and was not returned; guess further
                    else:
                        return json_or_text_guesser()
                # Probably we're getting a response (like a 414) before cruise-control
                # can decorate the headers with version information
                else:
                    return json_or_text_guesser()

        response = inner_request_helper()
        final_response = is_response_final(response)
        while not final_response:
            display_response(response)
            response = inner_request_helper()
            final_response = is_response_final(response)

        # return the requests.response object
        return response