예제 #1
0
class SystemClient(object):
    """High-level client for generating requests for a beer-garden System.

    SystemClient creation:
        This class is intended to be the main way to create beer-garden requests. Create an
        instance with beer-garden connection information (optionally including a url_prefix) and
        a system name::

            client = SystemClient(host, port, 'example_system', ssl_enabled=True, url_prefix=None)

        Pass additional keyword arguments for more granularity:

            version_constraint:
                Allows specifying a particular system version. Can be a version literal ('1.0.0')
                or the special value 'latest.' Using 'latest' will allow the the SystemClient to
                retry a request if it fails due to a missing system (see Creating Requests).

            default_instance:
                The instance name to use when creating a request if no other instance name is
                specified. Since each request must be addressed to a specific instance this is a
                convenience to prevent needing to specify the 'default' instance for each request.

            always_update:
                Always attempt to reload the system definition before making a request. This is
                useful to ensure Requests are always made against the latest version of the system.
                If not set the System definition will be loaded once (upon making the first
                request) and then only reloaded if a Request fails.

    Loading the System:
        The System definition is lazily loaded, so nothing happens until the first attempt to send
        a Request. At that point the SystemClient will query beer-garden to get a system definition
        that matches the system_name and version_constraint. If no matching system can be found a
        FetchError will be raised. If always_update was set to True this will happen
        before making each request, not just the first.

    Making a Request:
        The standard way to create and send requests is by calling object attributes::

            request = client.example_command(param_1='example_param')

        In the normal case this will block until the request completes. Request completion is
        determined by periodically polling beer-garden to check the Request status. The time
        between polling requests starts at 0.5s and doubles each time the request has still not
        completed, up to max_delay. If a timeout was specified and the Request has not completed
        within that time a ``ConnectionTimeoutError`` will be raised.

        It is also possible to create the SystemClient in non-blocking mode by specifying
        blocking=False. In this case the request creation will immediately return a Future and
        will spawn a separate thread to poll for Request completion. The max_concurrent parameter
        is used to control the maximum threads available for polling.

        .. code-block:: python

            # Create a SystemClient with blocking=False
            client = SystemClient(host, port, 'example_system', ssl_enabled=True, blocking=False)

            # Create and send 5 requests without waiting for request completion
            futures = [client.example_command(param_1=number) for number in range(5)]

            # Now wait on all requests to complete
            concurrent.futures.wait(futures)

        If the request creation process fails (e.g. the command failed validation) and
        version_constraint is 'latest' then the SystemClient will check to see if a different
        version is available, and if so it will attempt to make the request on that version.
        This is so users of the SystemClient that don't necessarily care about the target system
        version don't need to be restarted if the target system is updated.

    Tweaking beer-garden Request Parameters:
        There are several parameters that control how beer-garden routes / processes a request. To
        denote these as intended for beer-garden itself (rather than a parameter to be passed to
        the Plugin) prepend a leading underscore to the argument name.

        Sending to another instance::

            request = client.example_command(_instance_name='instance_2', param_1='example_param')

        Request with a comment::

            request = client.example_command(_comment='I'm a beer-garden comment!',
                                             param_1='example_param')

        Without the leading underscore the arguments would be treated the same as param_1 -
        another parameter to be passed to the plugin.

    :param host: beer-garden REST API hostname.
    :param port: beer-garden REST API port.
    :param system_name: The name of the system to use.
    :param version_constraint: The system version to use. Can be specific or 'latest'.
    :param default_instance: The instance to use if not specified when creating a request.
    :param always_update: Should check for a newer System version before each request.
    :param timeout: Length of time to wait for a request to complete. 'None' means wait forever.
    :param max_delay: Maximum time to wait between checking the status of a created request.
    :param api_version: beer-garden API version.
    :param ssl_enabled: Flag indicating whether to use HTTPS when communicating with beer-garden.
    :param ca_cert: beer-garden REST API server CA certificate.
    :param blocking: Block after request creation until the request completes.
    :param max_concurrent: Maximum number of concurrent requests allowed.
    :param client_cert: The client certificate to use when making requests.
    :param url_prefix: beer-garden REST API URL Prefix.
    :param ca_verify: Flag indicating whether to verify server certificate when making a request.
    :param raise_on_error: Raises an error if the request ends in an error state.
    :param username: Username for Beergarden authentication
    :param password: Password for Beergarden authentication
    :param access_token: Access token for Beergarden authentication
    :param refresh_token: Refresh token for Beergarden authentication
    :param client_timeout: Max time to will wait for server response
    """
    def __init__(self,
                 bg_host=None,
                 bg_port=None,
                 system_name=None,
                 version_constraint='latest',
                 default_instance='default',
                 always_update=False,
                 timeout=None,
                 max_delay=30,
                 api_version=None,
                 ssl_enabled=False,
                 ca_cert=None,
                 blocking=True,
                 max_concurrent=None,
                 client_cert=None,
                 url_prefix=None,
                 ca_verify=True,
                 raise_on_error=False,
                 **kwargs):
        self._system_name = system_name
        self._version_constraint = version_constraint
        self._default_instance = default_instance
        self._always_update = always_update
        self._timeout = timeout
        self._max_delay = max_delay
        self._blocking = blocking
        self._raise_on_error = raise_on_error
        self._bg_host = bg_host or kwargs.get('host')
        self._bg_port = bg_port or kwargs.get('port')
        self.logger = logging.getLogger(__name__)

        self._loaded = False
        self._system = None
        self._commands = None

        # This is for Python 3.4 compatibility - max_workers MUST be non-None
        # in that version. This logic is what was added in Python 3.5
        if max_concurrent is None:
            max_concurrent = (cpu_count() or 1) * 5
        self._thread_pool = ThreadPoolExecutor(max_workers=max_concurrent)

        self._easy_client = EasyClient(bg_host=self._bg_host,
                                       bg_port=self._bg_port,
                                       ssl_enabled=ssl_enabled,
                                       api_version=api_version,
                                       ca_cert=ca_cert,
                                       client_cert=client_cert,
                                       url_prefix=url_prefix,
                                       ca_verify=ca_verify,
                                       **kwargs)

    def __getattr__(self, item):
        """Standard way to create and send beer-garden requests"""
        return self.create_bg_request(item)

    def create_bg_request(self, command_name, **kwargs):
        """Create a callable that will execute a beer-garden request when called.

        Normally you interact with the SystemClient by accessing attributes, but there could be
        certain cases where you want to create a request without sending it.

        Example::

            client = SystemClient(host, port, 'system', blocking=False)
            requests = []

            # No arguments
            requests.append(client.create_bg_request('command_1'))

            # arg_1 will be passed as a parameter
            requests.append(client.create_bg_request('command_2', arg_1='Hi!'))

            futures = [request() for request in requests]   # Calling creates and sends the request
            concurrent.futures.wait(futures)                # Wait for all the futures to complete

        :param command_name: The name of the command that will be sent.
        :param kwargs: Additional arguments to pass to send_bg_request.
        :raise AttributeError: The system does not have a command with the given command_name.
        :return: A partial that will create and execute a beer-garden request when called.
        """

        if not self._loaded or self._always_update:
            self.load_bg_system()

        if command_name in self._commands:
            return partial(
                self.send_bg_request,
                _command=command_name,
                _system_name=self._system.name,
                _system_version=self._system.version,
                _system_display=self._system.display_name,
                _output_type=self._commands[command_name].output_type,
                _instance_name=self._default_instance,
                **kwargs)
        else:
            raise AttributeError(
                "System '%s' version '%s' has no command named '%s'" %
                (self._system.name, self._system.version, command_name))

    def send_bg_request(self, **kwargs):
        """Actually create a Request and send it to beer-garden

        .. note::
            This method is intended for advanced use only, mainly cases where you're using the
            SystemClient without a predefined System. It assumes that everything needed to
            construct the request is being passed in kwargs. If this doesn't sound like what you
            want you should check out create_bg_request.

        :param kwargs: All necessary request parameters, including beer-garden internal parameters
        :raise ValidationError: If the Request creation failed validation on the server
        :return: If the SystemClient was created with blocking=True a completed request object,
            otherwise a Future that will return the Request when it completes.
        """
        # Need to pop here, otherwise we'll try to send as a request parameter
        raise_on_error = kwargs.pop('_raise_on_error', self._raise_on_error)
        blocking = kwargs.pop('_blocking', self._blocking)
        timeout = kwargs.pop('_timeout', self._timeout)

        # If the request fails validation and the version constraint allows,
        # check for a new version and retry
        try:
            request = self._construct_bg_request(**kwargs)
            request = self._easy_client.create_request(request,
                                                       blocking=blocking,
                                                       timeout=timeout)
        except ValidationError:
            if self._system and self._version_constraint == 'latest':
                old_version = self._system.version

                self.load_bg_system()

                if old_version != self._system.version:
                    kwargs['_system_version'] = self._system.version
                    return self.send_bg_request(**kwargs)
            raise

        # If not blocking just return the future
        if not blocking:
            return self._thread_pool.submit(self._wait_for_request, request,
                                            raise_on_error, timeout)

        # Brew-view before 2.4 doesn't support the blocking flag, so make sure
        # the request is actually complete before returning
        return self._wait_for_request(request, raise_on_error, timeout)

    def load_bg_system(self):
        """Query beer-garden for a System definition

        This method will make the query to beer-garden for a System matching the name and version
        constraints specified during SystemClient instance creation.

        If this method completes successfully the SystemClient will be ready to create and send
        Requests.

        :raise FetchError: If unable to find a matching System
        :return: None
        """

        if self._version_constraint == 'latest':
            systems = self._easy_client.find_systems(name=self._system_name)
            self._system = sorted(systems,
                                  key=lambda x: x.version,
                                  reverse=True)[0] if systems else None
        else:
            self._system = self._easy_client.find_unique_system(
                name=self._system_name, version=self._version_constraint)

        if self._system is None:
            raise FetchError(
                "Beer-garden has no system named '%s' with a version matching '%s'"
                % (self._system_name, self._version_constraint))

        self._commands = {
            command.name: command
            for command in self._system.commands
        }
        self._loaded = True

    def _wait_for_request(self, request, raise_on_error, timeout):
        """Poll the server until the request is completed or errors"""

        delay_time = 0.5
        total_wait_time = 0
        while request.status not in Request.COMPLETED_STATUSES:

            if timeout and total_wait_time > timeout:
                raise TimeoutExceededError("Timeout waiting for request '%s' "
                                           "to complete" % str(request))

            time.sleep(delay_time)
            total_wait_time += delay_time
            delay_time = min(delay_time * 2, self._max_delay)

            request = self._easy_client.find_unique_request(id=request.id)

        if raise_on_error and request.status == 'ERROR':
            raise RequestFailedError(request)

        return request

    def _get_parent_for_request(self):
        parent = getattr(request_context, 'current_request', None)
        if parent is None:
            return None

        if (request_context.bg_host.upper() != self._bg_host.upper()
                or request_context.bg_port != self._bg_port):
            self.logger.warning(
                "A parent request was found, but the destination beer-garden "
                "appears to be different than the beer-garden to which this plugin "
                "is assigned. Cross-server parent/child requests are not supported "
                "at this time. Removing the parent context so the request doesn't "
                "fail.")
            return None

        return Request(id=str(parent.id))

    def _construct_bg_request(self, **kwargs):
        """Create a request that can be used with EasyClient.create_request"""

        command = kwargs.pop('_command', None)
        system_name = kwargs.pop('_system_name', None)
        system_version = kwargs.pop('_system_version', None)
        system_display = kwargs.pop('_system_display', None)
        instance_name = kwargs.pop('_instance_name', None)
        comment = kwargs.pop('_comment', None)
        output_type = kwargs.pop('_output_type', None)
        metadata = kwargs.pop('_metadata', {})

        parent = self._get_parent_for_request()

        if system_display:
            metadata['system_display_name'] = system_display

        if command is None:
            raise ValidationError('Unable to send a request with no command')
        if system_name is None:
            raise ValidationError(
                'Unable to send a request with no system name')
        if system_version is None:
            raise ValidationError(
                'Unable to send a request with no system version')
        if instance_name is None:
            raise ValidationError(
                'Unable to send a request with no instance name')

        return Request(command=command,
                       system=system_name,
                       system_version=system_version,
                       instance_name=instance_name,
                       comment=comment,
                       output_type=output_type,
                       parent=parent,
                       metadata=metadata,
                       parameters=kwargs)
예제 #2
0
class EasyClientTest(unittest.TestCase):
    def setUp(self):
        self.parser = Mock(name="parser", spec=SchemaParser)
        self.client = EasyClient(
            host="localhost", port="3000", api_version=1, parser=self.parser
        )
        self.fake_success_response = Mock(
            ok=True, status_code=200, json=Mock(return_value="payload")
        )
        self.fake_client_error_response = Mock(
            ok=False, status_code=400, json=Mock(return_value="payload")
        )
        self.fake_not_found_error_response = Mock(
            ok=False, status_code=404, json=Mock(return_value="payload")
        )
        self.fake_wait_exceeded_response = Mock(
            ok=False, status_code=408, json=Mock(return_value="payload")
        )
        self.fake_conflict_error_response = Mock(
            ok=False, status_code=409, json=Mock(return_value="payload")
        )
        self.fake_too_large_error_response = Mock(
            ok=False, status_code=413, json=Mock(return_value="payload")
        )
        self.fake_server_error_response = Mock(
            ok=False, status_code=500, json=Mock(return_value="payload")
        )
        self.fake_connection_error_response = Mock(
            ok=False, status_code=503, json=Mock(return_value="payload")
        )

    @patch("brewtils.rest.client.RestClient.get_logging_config")
    def test_get_logging_config_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(
            RestConnectionError, self.client.get_logging_config, "system_name"
        )

    # Find systems
    @patch("brewtils.rest.client.RestClient.get_systems")
    def test_find_systems_call_get_systems(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.client.find_systems()
        mock_get.assert_called()

    @patch("brewtils.rest.client.RestClient.get_systems")
    def test_find_systems_with_params_get_systems(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.client.find_systems(name="foo")
        mock_get.assert_called_with(name="foo")

    @patch("brewtils.rest.client.RestClient.get_systems")
    def test_find_systems_server_error(self, mock_get):
        mock_get.return_value = self.fake_server_error_response
        self.assertRaises(FetchError, self.client.find_systems)

    @patch("brewtils.rest.client.RestClient.get_systems")
    def test_find_systems_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.find_systems)

    @patch("brewtils.rest.client.RestClient.get_systems")
    def test_find_systems_call_parser(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.client.find_systems()
        self.parser.parse_system.assert_called_with("payload", many=True)

    @patch("brewtils.rest.easy_client.EasyClient._find_system_by_id")
    def test_find_unique_system_by_id(self, find_mock):
        system_mock = Mock()
        find_mock.return_value = system_mock

        self.assertEqual(system_mock, self.client.find_unique_system(id="id"))
        find_mock.assert_called_with("id")

    def test_find_unique_system_none(self):
        self.client.find_systems = Mock(return_value=None)
        self.assertIsNone(self.client.find_unique_system())

    def test_find_unique_system_one(self):
        self.client.find_systems = Mock(return_value=["system1"])
        self.assertEqual("system1", self.client.find_unique_system())

    def test_find_unique_system_multiple(self):
        self.client.find_systems = Mock(return_value=["system1", "system2"])
        self.assertRaises(FetchError, self.client.find_unique_system)

    @patch("brewtils.rest.client.RestClient.get_system")
    def test_find_system_by_id(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.parser.parse_system = Mock(return_value="system")

        self.assertEqual(self.client._find_system_by_id("id", foo="bar"), "system")
        self.parser.parse_system.assert_called_with("payload", many=False)
        mock_get.assert_called_with("id", foo="bar")

    @patch("brewtils.rest.client.RestClient.get_system")
    def test_find_system_by_id_404(self, mock_get):
        mock_get.return_value = self.fake_not_found_error_response

        self.assertIsNone(self.client._find_system_by_id("id", foo="bar"))
        mock_get.assert_called_with("id", foo="bar")

    @patch("brewtils.rest.client.RestClient.get_system")
    def test_find_system_by_id_server_error(self, mock_get):
        mock_get.return_value = self.fake_server_error_response

        self.assertRaises(FetchError, self.client._find_system_by_id, "id")
        mock_get.assert_called_with("id")

    @patch("brewtils.rest.client.RestClient.get_system")
    def test_find_system_by_id_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client._find_system_by_id, "id")

    # Create system
    @patch("brewtils.rest.client.RestClient.post_systems")
    def test_create_system(self, mock_post):
        mock_post.return_value = self.fake_success_response
        self.parser.serialize_system = Mock(return_value="json_system")
        self.parser.parse_system = Mock(return_value="system_response")

        self.assertEqual("system_response", self.client.create_system("system"))
        self.parser.serialize_system.assert_called_with("system")
        self.parser.parse_system.assert_called_with("payload", many=False)

    @patch("brewtils.rest.client.RestClient.post_systems")
    def test_create_system_client_error(self, mock_post):
        mock_post.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.create_system, "system")

    @patch("brewtils.rest.client.RestClient.post_systems")
    def test_create_system_server_error(self, mock_post):
        mock_post.return_value = self.fake_server_error_response
        self.assertRaises(SaveError, self.client.create_system, "system")

    @patch("brewtils.rest.client.RestClient.post_systems")
    def test_create_system_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.create_system, "system")

    # Update request
    @patch("brewtils.rest.client.RestClient.patch_system")
    def test_update_system(self, mock_patch):
        mock_patch.return_value = self.fake_success_response
        self.parser.serialize_command = Mock(return_value="new_commands")

        self.client.update_system("id", new_commands="new_commands")
        self.parser.parse_system.assert_called_with("payload", many=False)
        self.assertEqual(1, mock_patch.call_count)
        payload = mock_patch.call_args[0][1]
        self.assertNotEqual(-1, payload.find("new_commands"))

    @patch("brewtils.rest.easy_client.PatchOperation")
    @patch("brewtils.rest.client.RestClient.patch_system")
    def test_update_system_metadata(self, mock_patch, MockPatch):
        MockPatch.return_value = "patch"
        mock_patch.return_value = self.fake_success_response
        metadata = {"foo": "bar"}

        self.client.update_system("id", new_commands=None, metadata=metadata)
        MockPatch.assert_called_with("update", "/metadata", {"foo": "bar"})
        self.parser.serialize_patch.assert_called_with(["patch"], many=True)
        self.parser.parse_system.assert_called_with("payload", many=False)

    @patch("brewtils.rest.easy_client.PatchOperation")
    @patch("brewtils.rest.client.RestClient.patch_system")
    def test_update_system_kwargs(self, mock_patch, MockPatch):
        MockPatch.return_value = "patch"
        mock_patch.return_value = self.fake_success_response

        self.client.update_system("id", new_commands=None, display_name="foo")
        MockPatch.assert_called_with("replace", "/display_name", "foo")
        self.parser.serialize_patch.assert_called_with(["patch"], many=True)
        self.parser.parse_system.assert_called_with("payload", many=False)

    @patch("brewtils.rest.client.RestClient.patch_system")
    def test_update_system_client_error(self, mock_patch):
        mock_patch.return_value = self.fake_client_error_response

        self.assertRaises(ValidationError, self.client.update_system, "id")
        mock_patch.assert_called_once_with("id", ANY)

    @patch("brewtils.rest.client.RestClient.patch_system")
    def test_update_system_invalid_id(self, mock_patch):
        mock_patch.return_value = self.fake_not_found_error_response

        self.assertRaises(NotFoundError, self.client.update_system, "id")
        mock_patch.assert_called_once_with("id", ANY)

    @patch("brewtils.rest.client.RestClient.patch_system")
    def test_update_system_conflict(self, mock_patch):
        mock_patch.return_value = self.fake_conflict_error_response

        self.assertRaises(ConflictError, self.client.update_system, "id")
        mock_patch.assert_called_once_with("id", ANY)

    @patch("brewtils.rest.client.RestClient.patch_system")
    def test_update_system_server_error(self, mock_patch):
        mock_patch.return_value = self.fake_server_error_response

        self.assertRaises(SaveError, self.client.update_system, "id")
        mock_patch.assert_called_once_with("id", ANY)

    @patch("brewtils.rest.client.RestClient.patch_system")
    def test_update_system_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.update_system, "system")

    # Remove system
    @patch("brewtils.rest.easy_client.EasyClient._remove_system_by_id")
    @patch("brewtils.rest.easy_client.EasyClient.find_unique_system")
    def test_remove_system(self, find_mock, remove_mock):
        find_mock.return_value = System(id="id")
        remove_mock.return_value = "delete_response"

        self.assertEqual(
            "delete_response", self.client.remove_system(search="search params")
        )
        find_mock.assert_called_once_with(search="search params")
        remove_mock.assert_called_once_with("id")

    @patch("brewtils.rest.easy_client.EasyClient._remove_system_by_id")
    @patch("brewtils.rest.easy_client.EasyClient.find_unique_system")
    def test_remove_system_none_found(self, find_mock, remove_mock):
        find_mock.return_value = None

        self.assertRaises(FetchError, self.client.remove_system, search="search params")
        self.assertFalse(remove_mock.called)
        find_mock.assert_called_once_with(search="search params")

    @patch("brewtils.rest.client.RestClient.delete_system")
    def test_remove_system_by_id(self, mock_delete):
        mock_delete.return_value = self.fake_success_response

        self.assertTrue(self.client._remove_system_by_id("foo"))
        mock_delete.assert_called_with("foo")

    @patch("brewtils.rest.client.RestClient.delete_system")
    def test_remove_system_by_id_client_error(self, mock_remove):
        mock_remove.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client._remove_system_by_id, "foo")

    @patch("brewtils.rest.client.RestClient.delete_system")
    def test_remove_system_by_id_server_error(self, mock_remove):
        mock_remove.return_value = self.fake_server_error_response
        self.assertRaises(DeleteError, self.client._remove_system_by_id, "foo")

    @patch("brewtils.rest.client.RestClient.delete_system")
    def test_remove_system_by_id_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client._remove_system_by_id, "foo")

    def test_remove_system_by_id_none(self):
        self.assertRaises(DeleteError, self.client._remove_system_by_id, None)

    # Initialize instance
    @patch("brewtils.rest.client.RestClient.patch_instance")
    def test_initialize_instance(self, request_mock):
        request_mock.return_value = self.fake_success_response

        self.client.initialize_instance("id")
        self.assertTrue(self.parser.parse_instance.called)
        request_mock.assert_called_once_with("id", ANY)

    @patch("brewtils.rest.client.RestClient.patch_instance")
    def test_initialize_instance_client_error(self, request_mock):
        request_mock.return_value = self.fake_client_error_response

        self.assertRaises(ValidationError, self.client.initialize_instance, "id")
        self.assertFalse(self.parser.parse_instance.called)
        request_mock.assert_called_once_with("id", ANY)

    @patch("brewtils.rest.client.RestClient.patch_instance")
    def test_initialize_instance_server_error(self, request_mock):
        request_mock.return_value = self.fake_server_error_response

        self.assertRaises(SaveError, self.client.initialize_instance, "id")
        self.assertFalse(self.parser.parse_instance.called)
        request_mock.assert_called_once_with("id", ANY)

    @patch("brewtils.rest.client.RestClient.patch_instance")
    def test_initialize_instance_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.initialize_instance, "id")

    @patch("brewtils.rest.client.RestClient.get_instance")
    def test_get_instance(self, request_mock):
        request_mock.return_value = self.fake_success_response

        self.client.get_instance("id")
        self.assertTrue(self.parser.parse_instance.called)
        request_mock.assert_called_once_with("id")

    @patch("brewtils.rest.client.RestClient.get_instance")
    def test_get_instance_client_error(self, request_mock):
        request_mock.return_value = self.fake_client_error_response

        self.assertRaises(ValidationError, self.client.get_instance, "id")
        self.assertFalse(self.parser.parse_instance.called)
        request_mock.assert_called_once_with("id")

    @patch("brewtils.rest.client.RestClient.get_instance")
    def test_get_instance_server_error(self, request_mock):
        request_mock.return_value = self.fake_server_error_response

        self.assertRaises(FetchError, self.client.get_instance, "id")
        self.assertFalse(self.parser.parse_instance.called)
        request_mock.assert_called_once_with("id")

    @patch("brewtils.rest.client.RestClient.get_instance")
    def test_get_instance_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.get_instance, "id")

    @patch("brewtils.rest.client.RestClient.get_instance")
    def test_get_instance_status(self, request_mock):
        request_mock.return_value = self.fake_success_response

        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")

            self.client.get_instance_status("id")
            self.assertTrue(self.parser.parse_instance.called)
            request_mock.assert_called_once_with("id")

            self.assertEqual(1, len(w))
            self.assertEqual(w[0].category, FutureWarning)

    @patch("brewtils.rest.client.RestClient.get_instance")
    def test_get_instance_status_client_error(self, request_mock):
        request_mock.return_value = self.fake_client_error_response

        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")

            self.assertRaises(ValidationError, self.client.get_instance_status, "id")
            self.assertFalse(self.parser.parse_instance.called)
            request_mock.assert_called_once_with("id")

            self.assertEqual(1, len(w))
            self.assertEqual(w[0].category, FutureWarning)

    @patch("brewtils.rest.client.RestClient.get_instance")
    def test_get_instance_status_server_error(self, request_mock):
        request_mock.return_value = self.fake_server_error_response

        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")

            self.assertRaises(FetchError, self.client.get_instance_status, "id")
            self.assertFalse(self.parser.parse_instance.called)
            request_mock.assert_called_once_with("id")

            self.assertEqual(1, len(w))
            self.assertEqual(w[0].category, FutureWarning)

    @patch("brewtils.rest.client.RestClient.get_instance")
    def test_get_instance_status_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response

        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter("always")

            self.assertRaises(
                RestConnectionError, self.client.get_instance_status, "id"
            )

            self.assertEqual(1, len(w))
            self.assertEqual(w[0].category, FutureWarning)

    @patch("brewtils.rest.client.RestClient.patch_instance")
    def test_update_instance_status(self, request_mock):
        request_mock.return_value = self.fake_success_response

        self.client.update_instance_status("id", "status")
        self.assertTrue(self.parser.parse_instance.called)
        request_mock.assert_called_once_with("id", ANY)

    @patch("brewtils.rest.client.RestClient.patch_instance")
    def test_update_instance_status_client_error(self, request_mock):
        request_mock.return_value = self.fake_client_error_response

        self.assertRaises(
            ValidationError, self.client.update_instance_status, "id", "status"
        )
        self.assertFalse(self.parser.parse_instance.called)
        request_mock.assert_called_once_with("id", ANY)

    @patch("brewtils.rest.client.RestClient.patch_instance")
    def test_update_instance_status_server_error(self, request_mock):
        request_mock.return_value = self.fake_server_error_response

        self.assertRaises(SaveError, self.client.update_instance_status, "id", "status")
        self.assertFalse(self.parser.parse_instance.called)
        request_mock.assert_called_once_with("id", ANY)

    @patch("brewtils.rest.client.RestClient.patch_instance")
    def test_update_instance_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(
            RestConnectionError, self.client.update_instance_status, "id", "status"
        )

    # Instance heartbeat
    @patch("brewtils.rest.client.RestClient.patch_instance")
    def test_instance_heartbeat(self, request_mock):
        request_mock.return_value = self.fake_success_response

        self.assertTrue(self.client.instance_heartbeat("id"))
        request_mock.assert_called_once_with("id", ANY)

    @patch("brewtils.rest.client.RestClient.patch_instance")
    def test_instance_heartbeat_client_error(self, request_mock):
        request_mock.return_value = self.fake_client_error_response

        self.assertRaises(ValidationError, self.client.instance_heartbeat, "id")
        request_mock.assert_called_once_with("id", ANY)

    @patch("brewtils.rest.client.RestClient.patch_instance")
    def test_instance_heartbeat_server_error(self, request_mock):
        request_mock.return_value = self.fake_server_error_response

        self.assertRaises(SaveError, self.client.instance_heartbeat, "id")
        request_mock.assert_called_once_with("id", ANY)

    @patch("brewtils.rest.client.RestClient.patch_instance")
    def test_instance_heartbeat_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.instance_heartbeat, "id")

    @patch("brewtils.rest.client.RestClient.delete_instance")
    def test_remove_instance(self, mock_delete):
        mock_delete.return_value = self.fake_success_response

        self.assertTrue(self.client.remove_instance("foo"))
        mock_delete.assert_called_with("foo")

    @patch("brewtils.rest.client.RestClient.delete_instance")
    def test_remove_instance_client_error(self, mock_remove):
        mock_remove.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.remove_instance, "foo")

    @patch("brewtils.rest.client.RestClient.delete_instance")
    def test_remove_instance_server_error(self, mock_remove):
        mock_remove.return_value = self.fake_server_error_response
        self.assertRaises(DeleteError, self.client.remove_instance, "foo")

    @patch("brewtils.rest.client.RestClient.delete_instance")
    def test_remove_instance_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.remove_instance, "foo")

    def test_remove_instance_none(self):
        self.assertRaises(DeleteError, self.client.remove_instance, None)

    # Find requests
    @patch("brewtils.rest.easy_client.EasyClient._find_request_by_id")
    def test_find_unique_request_by_id(self, find_mock):
        self.client.find_unique_request(id="id")
        find_mock.assert_called_with("id")

    def test_find_unique_request_none(self):
        self.client.find_requests = Mock(return_value=None)
        self.assertIsNone(self.client.find_unique_request())

    def test_find_unique_request_one(self):
        self.client.find_requests = Mock(return_value=["request1"])
        self.assertEqual("request1", self.client.find_unique_request())

    def test_find_unique_request_multiple(self):
        self.client.find_requests = Mock(return_value=["request1", "request2"])
        self.assertRaises(FetchError, self.client.find_unique_request)

    @patch("brewtils.rest.client.RestClient.get_requests")
    def test_find_requests(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.parser.parse_request = Mock(return_value="request")

        self.assertEqual("request", self.client.find_requests(search="params"))
        self.parser.parse_request.assert_called_with("payload", many=True)
        mock_get.assert_called_with(search="params")

    @patch("brewtils.rest.client.RestClient.get_requests")
    def test_find_requests_error(self, mock_get):
        mock_get.return_value = self.fake_server_error_response

        self.assertRaises(FetchError, self.client.find_requests, search="params")
        mock_get.assert_called_with(search="params")

    @patch("brewtils.rest.client.RestClient.get_requests")
    def test_find_requests_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(
            RestConnectionError, self.client.find_requests, search="params"
        )

    @patch("brewtils.rest.client.RestClient.get_request")
    def test_find_request_by_id(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.parser.parse_request = Mock(return_value="request")

        self.assertEqual(self.client._find_request_by_id("id"), "request")
        self.parser.parse_request.assert_called_with("payload", many=False)
        mock_get.assert_called_with("id")

    @patch("brewtils.rest.client.RestClient.get_request")
    def test_find_request_by_id_404(self, mock_get):
        mock_get.return_value = self.fake_not_found_error_response

        self.assertIsNone(self.client._find_request_by_id("id"))
        mock_get.assert_called_with("id")

    @patch("brewtils.rest.client.RestClient.get_request")
    def test_find_request_by_id_server_error(self, mock_get):
        mock_get.return_value = self.fake_server_error_response

        self.assertRaises(FetchError, self.client._find_request_by_id, "id")
        mock_get.assert_called_with("id")

    @patch("brewtils.rest.client.RestClient.get_request")
    def test_find_request_by_id_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client._find_request_by_id, "id")

    # Create request
    @patch("brewtils.rest.client.RestClient.post_requests")
    def test_create_request(self, mock_post):
        mock_post.return_value = self.fake_success_response
        self.parser.serialize_request = Mock(return_value="json_request")
        self.parser.parse_request = Mock(return_value="request_response")

        self.assertEqual("request_response", self.client.create_request("request"))
        self.parser.serialize_request.assert_called_with("request")
        self.parser.parse_request.assert_called_with("payload", many=False)

    @patch("brewtils.rest.client.RestClient.post_requests")
    def test_create_request_errors(self, mock_post):
        mock_post.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.create_request, "request")

        mock_post.return_value = self.fake_wait_exceeded_response
        self.assertRaises(WaitExceededError, self.client.create_request, "request")

        mock_post.return_value = self.fake_server_error_response
        self.assertRaises(SaveError, self.client.create_request, "request")

        mock_post.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.create_request, "request")

    # Update request
    @patch("brewtils.rest.client.RestClient.patch_request")
    def test_update_request(self, request_mock):
        request_mock.return_value = self.fake_success_response

        self.client.update_request(
            "id", status="new_status", output="new_output", error_class="ValueError"
        )
        self.parser.parse_request.assert_called_with("payload", many=False)
        self.assertEqual(1, request_mock.call_count)
        payload = request_mock.call_args[0][1]
        self.assertNotEqual(-1, payload.find("new_status"))
        self.assertNotEqual(-1, payload.find("new_output"))
        self.assertNotEqual(-1, payload.find("ValueError"))

    @patch("brewtils.rest.client.RestClient.patch_request")
    def test_update_request_client_error(self, request_mock):
        request_mock.return_value = self.fake_client_error_response

        self.assertRaises(ValidationError, self.client.update_request, "id")
        request_mock.assert_called_once_with("id", ANY)

    @patch("brewtils.rest.client.RestClient.patch_request")
    def test_update_request_too_large_error(self, request_mock):
        request_mock.return_value = self.fake_too_large_error_response

        self.assertRaises(TooLargeError, self.client.update_request, "id")
        request_mock.assert_called_once_with("id", ANY)

    @patch("brewtils.rest.client.RestClient.patch_request")
    def test_update_request_server_error(self, request_mock):
        request_mock.return_value = self.fake_server_error_response

        self.assertRaises(SaveError, self.client.update_request, "id")
        request_mock.assert_called_once_with("id", ANY)

    @patch("brewtils.rest.client.RestClient.patch_request")
    def test_update_request_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.update_request, "id")

    # Publish Event
    @patch("brewtils.rest.client.RestClient.post_event")
    def test_publish_event(self, mock_post):
        mock_post.return_value = self.fake_success_response
        self.assertTrue(self.client.publish_event(Mock()))

    @patch("brewtils.rest.client.RestClient.post_event")
    def test_publish_event_errors(self, mock_post):
        mock_post.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.publish_event, "system")

        mock_post.return_value = self.fake_server_error_response
        self.assertRaises(RestError, self.client.publish_event, "system")

        mock_post.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.publish_event, "system")

    # Queues
    @patch("brewtils.rest.client.RestClient.get_queues")
    def test_get_queues(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.client.get_queues()
        self.assertTrue(self.parser.parse_queue.called)

    @patch("brewtils.rest.client.RestClient.get_queues")
    def test_get_queues_errors(self, mock_get):
        mock_get.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.get_queues)

        mock_get.return_value = self.fake_server_error_response
        self.assertRaises(RestError, self.client.get_queues)

        mock_get.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.get_queues)

    @patch("brewtils.rest.client.RestClient.delete_queue")
    def test_clear_queue(self, mock_delete):
        mock_delete.return_value = self.fake_success_response
        self.assertTrue(self.client.clear_queue("queue"))

    @patch("brewtils.rest.client.RestClient.delete_queue")
    def test_clear_queue_errors(self, mock_delete):
        mock_delete.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.clear_queue, "queue")

        mock_delete.return_value = self.fake_server_error_response
        self.assertRaises(RestError, self.client.clear_queue, "queue")

        mock_delete.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.clear_queue, "queue")

    @patch("brewtils.rest.client.RestClient.delete_queues")
    def test_clear_all_queues(self, mock_delete):
        mock_delete.return_value = self.fake_success_response
        self.assertTrue(self.client.clear_all_queues())

    @patch("brewtils.rest.client.RestClient.delete_queues")
    def test_clear_all_queues_errors(self, mock_delete):
        mock_delete.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.clear_all_queues)

        mock_delete.return_value = self.fake_server_error_response
        self.assertRaises(RestError, self.client.clear_all_queues)

        mock_delete.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.clear_all_queues)

    # Find Jobs
    @patch("brewtils.rest.client.RestClient.get_jobs")
    def test_find_jobs(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.parser.parse_job = Mock(return_value="job")

        self.assertEqual("job", self.client.find_jobs(search="params"))
        self.parser.parse_job.assert_called_with("payload", many=True)
        mock_get.assert_called_with(search="params")

    @patch("brewtils.rest.client.RestClient.get_jobs")
    def test_find_jobs_error(self, mock_get):
        mock_get.return_value = self.fake_server_error_response

        self.assertRaises(FetchError, self.client.find_jobs, search="params")
        mock_get.assert_called_with(search="params")

    # Create Jobs
    @patch("brewtils.rest.client.RestClient.post_jobs")
    def test_create_job(self, mock_post):
        mock_post.return_value = self.fake_success_response
        self.parser.serialize_job = Mock(return_value="json_job")
        self.parser.parse_job = Mock(return_value="job_response")

        self.assertEqual("job_response", self.client.create_job("job"))
        self.parser.serialize_job.assert_called_with("job")
        self.parser.parse_job.assert_called_with("payload", many=False)

    @patch("brewtils.rest.client.RestClient.post_jobs")
    def test_create_job_error(self, mock_post):
        mock_post.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.create_job, "job")

    # Remove Job
    @patch("brewtils.rest.client.RestClient.delete_job")
    def test_delete_job(self, mock_delete):
        mock_delete.return_value = self.fake_success_response
        self.assertEqual(True, self.client.remove_job("job_id"))

    @patch("brewtils.rest.client.RestClient.delete_job")
    def test_delete_job_error(self, mock_delete):
        mock_delete.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.remove_job, "job_id")

    # Pause Job
    @patch("brewtils.rest.easy_client.PatchOperation")
    @patch("brewtils.rest.client.RestClient.patch_job")
    def test_pause_job(self, mock_patch, MockPatch):
        MockPatch.return_value = "patch"
        mock_patch.return_value = self.fake_success_response

        self.client.pause_job("id")
        MockPatch.assert_called_with("update", "/status", "PAUSED")
        self.parser.serialize_patch.assert_called_with(["patch"], many=True)
        self.parser.parse_job.assert_called_with("payload", many=False)

    @patch("brewtils.rest.client.RestClient.patch_job")
    def test_pause_job_error(self, mock_patch):
        mock_patch.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.pause_job, "id")

    @patch("brewtils.rest.easy_client.PatchOperation")
    @patch("brewtils.rest.client.RestClient.patch_job")
    def test_resume_job(self, mock_patch, MockPatch):
        MockPatch.return_value = "patch"
        mock_patch.return_value = self.fake_success_response

        self.client.resume_job("id")
        MockPatch.assert_called_with("update", "/status", "RUNNING")
        self.parser.serialize_patch.assert_called_with(["patch"], many=True)
        self.parser.parse_job.assert_called_with("payload", many=False)

    # Users
    @patch("brewtils.rest.client.RestClient.get_user")
    def test_who_am_i(self, mock_get):
        self.client.who_am_i()
        mock_get.assert_called_with("anonymous")

    @patch("brewtils.rest.client.RestClient.get_user")
    def test_get_user(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.client.get_user("identifier")
        self.assertTrue(self.parser.parse_principal.called)

    @patch("brewtils.rest.client.RestClient.get_user")
    def test_get_user_errors(self, mock_get):
        mock_get.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.get_user, "identifier")

        mock_get.return_value = self.fake_not_found_error_response
        self.assertRaises(NotFoundError, self.client.get_user, "identifier")
예제 #3
0
class SystemClient(object):
    """High-level client for generating requests for a Beer-garden System.

    SystemClient creation:
        This class is intended to be the main way to create Beer-garden requests. Create
        an instance with Beer-garden connection information and a system name::

            client = SystemClient(
                system_name='example_system',
                system_namespace='default',
                bg_host="host",
                bg_port=2337,
            )

        Note: Passing an empty string as the system_namespace parameter will evalutate
        to the local garden's default namespace.

        Pass additional keyword arguments for more granularity:

            version_constraint:
                Allows specifying a particular system version. Can be a version literal
                ('1.0.0') or the special value 'latest.' Using 'latest' will allow the
                SystemClient to retry a request if it fails due to a missing system
                (see Creating Requests).

            default_instance:
                The instance name to use when creating a request if no other instance
                name is specified. Since each request must be addressed to a specific
                instance this is a convenience to prevent needing to specify the
                instance for each request.

            always_update:
                If True the SystemClient will always attempt to reload the system
                definition before making a request. This is useful to ensure Requests
                are always made against the latest version of the system.
                If not set the System definition will be loaded when making the first
                request and will only be reloaded if a Request fails.

    Loading the System:
        The System definition is lazily loaded, so nothing happens until the first
        attempt to send a Request. At that point the SystemClient will query Beer-garden
        to get a system definition that matches the system_name and version_constraint.
        If no matching System can be found a FetchError will be raised. If always_update
        was set to True this will happen before making each request, not only the first.

    Making a Request:
        The standard way to create and send requests is by calling object attributes::

            request = client.example_command(param_1='example_param')

        In the normal case this will block until the request completes. Request
        completion is determined by periodically polling Beer-garden to check the
        Request status. The time between polling requests starts at 0.5s and doubles
        each time the request has still not completed, up to max_delay. If a timeout was
        specified and the Request has not completed within that time a
        ``ConnectionTimeoutError`` will be raised.

        It is also possible to create the SystemClient in non-blocking mode by
        specifying blocking=False. In this case the request creation will immediately
        return a Future and will spawn a separate thread to poll for Request completion.
        The max_concurrent parameter is used to control the maximum threads available
        for polling.

        .. code-block:: python

            # Create a SystemClient with blocking=False
            client = SystemClient(
                system_name='example_system',
                system_namespace='default',
                bg_host="localhost",
                bg_port=2337,
                blocking=False,
            )

            # Create and send 5 requests without waiting for request completion
            futures = [client.example_command(param_1=number) for number in range(5)]

            # Now wait on all requests to complete
            concurrent.futures.wait(futures)

        If the request creation process fails (e.g. the command failed validation) and
        version_constraint is 'latest' then the SystemClient will check to see if a
        newer version is available, and if so it will attempt to make the request on
        that version. This is so users of the SystemClient that don't necessarily care
        about the target system version don't need to be restarted every time the target
        system is updated.

        It's also possible to control what happens when a Request results in an ERROR.
        If the ``raise_on_error`` parameter is set to False (the default) then Requests
        that are not successful simply result in a Request with a status of ERROR, and
        it is the plugin developer's responsibility to check for this case. However, if
        ``raise_on_error`` is set to True then this will result in a
        ``RequestFailedError`` being raised. This will happen regardless of the value
        of the ``blocking`` flag.

    Tweaking Beer-garden Request Parameters:
        There are several parameters that control how beer-garden routes / processes a
        request. To denote these as intended for Beer-garden itself (rather than a
        parameter to be passed to the Plugin) prepend a leading underscore to the
        argument name.

        Sending to another instance::

            request = client.example_command(
                _instance_name="instance_2", param_1="example_param"
            )

        Request with a comment::

            request = client.example_command(
                _comment="I'm a beer-garden comment!", param_1="example_param"
            )

        Without the leading underscore the arguments would be treated the same as
        "param_1" - another parameter to be passed to the plugin.

        Request that raises::

            client = SystemClient(
                system_name="foo",
                system_namespace='default',
                bg_host="localhost",
                bg_port=2337,
            )

            try:
                client.command_that_errors(_raise_on_error=True)
            except RequestFailedError:
                print("I could have just ignored this")

    Args:
        system_name (str): Name of the System to make Requests on
        system_namespace (str): Namespace of the System to make Requests on
        version_constraint (str): System version to make Requests on. Can be specific
            ('1.0.0') or 'latest'.
        default_instance (str): Name of the Instance to make Requests on
        always_update (bool): Whether to check if a newer version of the System exists
            before making each Request. Only relevant if ``version_constraint='latest'``
        timeout (int): Seconds to wait for a request to complete. 'None' means wait
            forever.
        max_delay (int): Maximum number of seconds to wait between status checks for a
            created request
        blocking (bool): Flag indicating whether creation will block until the Request
            is complete or return a Future that will complete when the Request does
        max_concurrent (int): Maximum number of concurrent requests allowed.
            Only has an effect when blocking=False.
        raise_on_error (bool): Flag controlling whether created Requests that complete
            with an ERROR state should raise an exception

        bg_host (str): Beer-garden hostname
        bg_port (int): Beer-garden port
        bg_url_prefix (str): URL path that will be used as a prefix when communicating
            with Beer-garden. Useful if Beer-garden is running on a URL other than '/'.
        ssl_enabled (bool): Whether to use SSL for Beer-garden communication
        ca_cert (str): Path to certificate file containing the certificate of the
            authority that issued the Beer-garden server certificate
        ca_verify (bool): Whether to verify Beer-garden server certificate
        client_cert (str): Path to client certificate to use when communicating with
            Beer-garden
        api_version (int): Beer-garden API version to use
        client_timeout (int): Max time to wait for Beer-garden server response
        username (str): Username for Beer-garden authentication
        password (str): Password for Beer-garden authentication
        access_token (str): Access token for Beer-garden authentication
        refresh_token (str): Refresh token for Beer-garden authentication
    """
    def __init__(self, *args, **kwargs):
        self._logger = logging.getLogger(__name__)

        self._loaded = False
        self._system = None
        self._commands = {}

        # Need this for back-compatibility (see #836)
        if len(args) > 2:
            _deprecate(
                "Heads up - passing system_name as a positional argument is deprecated "
                "and will be removed in version 4.0", )
            kwargs.setdefault("system_name", args[2])

        # Now need to determine if the intended target is the current running plugin.
        # Start by ensuring there's a valid Plugin context active
        target_self = bool(brewtils.plugin.CONFIG)

        # If ANY of the target specification arguments don't match the current plugin
        # then the target is different
        config_map = {
            "system_name": "name",
            "version_constraint": "version",
            "default_instance": "instance_name",
            "system_namespace": "namespace",
        }
        for key, value in config_map.items():
            if (kwargs.get(key) is not None
                    and kwargs.get(key) != brewtils.plugin.CONFIG[value]):
                target_self = False
                break

        # Now assign self._system_name, etc based on the value of target_self
        if target_self:
            self._system_name = brewtils.plugin.CONFIG.name
            self._version_constraint = brewtils.plugin.CONFIG.version
            self._default_instance = brewtils.plugin.CONFIG.instance_name
            self._system_namespace = brewtils.plugin.CONFIG.namespace or ""
        else:
            self._system_name = kwargs.get("system_name")
            self._version_constraint = kwargs.get("version_constraint",
                                                  "latest")
            self._default_instance = kwargs.get("default_instance", "default")
            self._system_namespace = kwargs.get(
                "system_namespace", brewtils.plugin.CONFIG.namespace or "")

        self._always_update = kwargs.get("always_update", False)
        self._timeout = kwargs.get("timeout", None)
        self._max_delay = kwargs.get("max_delay", 30)
        self._blocking = kwargs.get("blocking", True)
        self._raise_on_error = kwargs.get("raise_on_error", False)

        # This is for Python 3.4 compatibility - max_workers MUST be non-None
        # in that version. This logic is what was added in Python 3.5
        max_concurrent = kwargs.get("max_concurrent", (cpu_count() or 1) * 5)
        self._thread_pool = ThreadPoolExecutor(max_workers=max_concurrent)

        # This points DeprecationWarnings at the right line
        kwargs.setdefault("stacklevel", 5)

        self._easy_client = EasyClient(*args, **kwargs)
        self._resolver = ResolutionManager(easy_client=self._easy_client)

    def __getattr__(self, item):
        # type: (str) -> partial
        """Standard way to create and send beer-garden requests"""
        return self.create_bg_request(item)

    def __str__(self):
        return "%s[%s]" % (self.bg_system, self.bg_default_instance)

    @property
    def bg_system(self):
        return self._system

    @property
    def bg_default_instance(self):
        return self._default_instance

    def create_bg_request(self, command_name, **kwargs):
        # type: (str, **Any) -> partial
        """Create a callable that will execute a Beer-garden request when called.

        Normally you interact with the SystemClient by accessing attributes, but there
        could be certain cases where you want to create a request without sending it.

        Example::

            client = SystemClient(host, port, 'system', blocking=False)

            # Create two callables - one with a parameter and one without
            uncreated_requests = [
                client.create_bg_request('command_1', arg_1='Hi!'),
                client.create_bg_request('command_2'),
            ]

            # Calling creates and sends the request
            # The result of each is a future because blocking=False on the SystemClient
            futures = [req() for req in uncreated_requests]

            # Wait for all the futures to complete
            concurrent.futures.wait(futures)

        Args:
            command_name (str): Name of the Command to send
            kwargs (dict): Will be passed as parameters when creating the Request

        Returns:
            Partial that will create and execute a Beer-garden request when called

        Raises:
            AttributeError: System does not have a Command with the given command_name
        """

        if not self._loaded or self._always_update:
            self.load_bg_system()

        if command_name in self._commands:
            return partial(
                self.send_bg_request,
                _command=command_name,
                _system_name=self._system.name,
                _system_namespace=self._system.namespace,
                _system_version=self._system.version,
                _system_display=self._system.display_name,
                _output_type=self._commands[command_name].output_type,
                _instance_name=self._default_instance,
                **kwargs)
        else:
            raise AttributeError("System '%s' has no command named '%s'" %
                                 (self._system, command_name))

    def send_bg_request(self, *args, **kwargs):
        """Actually create a Request and send it to Beer-garden

        .. note::
            This method is intended for advanced use only, mainly cases where you're
            using the SystemClient without a predefined System. It assumes that
            everything needed to construct the request is being passed in ``kwargs``. If
            this doesn't sound like what you want you should check out
            ``create_bg_request``.

        Args:
            args (list): Unused. Passing positional parameters indicates a bug
            kwargs (dict): All necessary request parameters, including Beer-garden
                internal parameters

        Returns:
            blocking=True: A completed Request object
            blocking=False: A future that will be completed when the Request does

        Raises:
            ValidationError: Request creation failed validation on the server
        """
        # First, if any positional args were given that's a bug, as it means someone
        # tried to pass a parameter without a key:
        # client.command_name(param)
        if args:
            raise RequestProcessException(
                "Using positional arguments when creating a request is not allowed. "
                "Please use keyword arguments instead.")

        # Need to pop here, otherwise we'll try to send as a request parameter
        raise_on_error = kwargs.pop("_raise_on_error", self._raise_on_error)
        blocking = kwargs.pop("_blocking", self._blocking)
        timeout = kwargs.pop("_timeout", self._timeout)

        # If the request fails validation and the version constraint allows,
        # check for a new version and retry
        try:
            request = self._construct_bg_request(**kwargs)
            request = self._easy_client.create_request(request,
                                                       blocking=blocking,
                                                       timeout=timeout)
        except ValidationError:
            if self._system and self._version_constraint == "latest":
                old_version = self._system.version

                self.load_bg_system()

                if old_version != self._system.version:
                    kwargs["_system_version"] = self._system.version
                    return self.send_bg_request(**kwargs)
            raise

        # If not blocking just return the future
        if not blocking:
            return self._thread_pool.submit(self._wait_for_request, request,
                                            raise_on_error, timeout)

        # Brew-view before 2.4 doesn't support the blocking flag, so make sure
        # the request is actually complete before returning
        return self._wait_for_request(request, raise_on_error, timeout)

    def load_bg_system(self):
        # type: () -> None
        """Query beer-garden for a System definition

        This method will make the query to beer-garden for a System matching the name
        and version constraints specified during SystemClient instance creation.

        If this method completes successfully the SystemClient will be ready to create
        and send Requests.

        Returns:
            None

        Raises:
            FetchError: Unable to find a matching System
        """

        if self._version_constraint == "latest":
            self._system = self._determine_latest(
                self._easy_client.find_systems(
                    name=self._system_name, namespace=self._system_namespace))
        else:
            self._system = self._easy_client.find_unique_system(
                name=self._system_name,
                version=self._version_constraint,
                namespace=self._system_namespace,
            )

        if self._system is None:
            raise FetchError(
                "Beer-garden has no system named '%s' with a version matching '%s' in "
                "namespace '%s'" % (
                    self._system_name,
                    self._version_constraint,
                    self._system_namespace
                    if self._system_namespace else "<garden default>",
                ))

        self._commands = {
            command.name: command
            for command in self._system.commands
        }
        self._loaded = True

    def _wait_for_request(self, request, raise_on_error, timeout):
        # type: (Request, bool, int) -> Request
        """Poll the server until the request is completed or errors"""

        delay_time = 0.5
        total_wait_time = 0
        while request.status not in Request.COMPLETED_STATUSES:

            if timeout and 0 < timeout < total_wait_time:
                raise TimeoutExceededError(
                    "Timeout waiting for request '%s' to complete" %
                    str(request))

            time.sleep(delay_time)
            total_wait_time += delay_time
            delay_time = min(delay_time * 2, self._max_delay)

            request = self._easy_client.find_unique_request(id=request.id)

        if raise_on_error and request.status == "ERROR":
            raise RequestFailedError(request)

        return request

    def _get_parent_for_request(self):
        # type: () -> Optional[Request]
        parent = getattr(brewtils.plugin.request_context, "current_request",
                         None)
        if parent is None:
            return None

        if brewtils.plugin.CONFIG and (
                brewtils.plugin.CONFIG.bg_host.upper() !=
                self._easy_client.client.bg_host.upper()
                or brewtils.plugin.CONFIG.bg_port !=
                self._easy_client.client.bg_port):
            self._logger.warning(
                "A parent request was found, but the destination beer-garden "
                "appears to be different than the beer-garden to which this plugin "
                "is assigned. Cross-server parent/child requests are not supported "
                "at this time. Removing the parent context so the request doesn't fail."
            )
            return None

        return Request(id=str(parent.id))

    def _construct_bg_request(self, **kwargs):
        # type: (**Any) -> Request
        """Create a request that can be used with EasyClient.create_request"""

        command = kwargs.pop("_command", None)
        system_name = kwargs.pop("_system_name", None)
        system_version = kwargs.pop("_system_version", None)
        system_display = kwargs.pop("_system_display", None)
        system_namespace = kwargs.pop("_system_namespace", None)
        instance_name = kwargs.pop("_instance_name", None)
        comment = kwargs.pop("_comment", None)
        output_type = kwargs.pop("_output_type", None)
        metadata = kwargs.pop("_metadata", {})
        parent = kwargs.pop("_parent", self._get_parent_for_request())

        if system_display:
            metadata["system_display_name"] = system_display

        # Don't check namespace - https://github.com/beer-garden/beer-garden/issues/827
        if command is None:
            raise ValidationError("Unable to send a request with no command")
        if system_name is None:
            raise ValidationError(
                "Unable to send a request with no system name")
        if system_version is None:
            raise ValidationError(
                "Unable to send a request with no system version")
        if instance_name is None:
            raise ValidationError(
                "Unable to send a request with no instance name")

        request = Request(
            command=command,
            system=system_name,
            system_version=system_version,
            namespace=system_namespace,
            instance_name=instance_name,
            comment=comment,
            output_type=output_type,
            parent=parent,
            metadata=metadata,
            parameters=kwargs,
        )

        request.parameters = self._resolve_parameters(command, request)

        return request

    def _resolve_parameters(self, command, request):
        # type: (str, Request) -> Dict[str, Any]
        """Attempt to upload any necessary file parameters

        This will inspect the Command model for the given command, looking for file
        parameters. Any file parameters will be "resolved" (aka uploaded) before
        continuing.

        If the command name can not be found in the current list of commands the
        parameter list will just be returned. This most likely indicates a direct
        invocation of send_bg_request since a bad command name should be caught earlier
        in the "normal" workflow.
        """
        if command not in self._commands:
            return request.parameters

        return self._resolver.resolve(request.parameters,
                                      self._commands[command].parameters,
                                      upload=True)

    @staticmethod
    def _determine_latest(systems):
        # type: (Iterable[System]) -> Optional[System]
        return (sorted(systems, key=lambda x: parse(x.version),
                       reverse=True)[0] if systems else None)
예제 #4
0
class EasyClientTest(unittest.TestCase):
    def setUp(self):
        self.parser = Mock()
        self.client = EasyClient(host='localhost',
                                 port='3000',
                                 api_version=1,
                                 parser=self.parser)
        self.fake_success_response = Mock(ok=True,
                                          status_code=200,
                                          json=Mock(return_value='payload'))
        self.fake_client_error_response = Mock(
            ok=False, status_code=400, json=Mock(return_value='payload'))
        self.fake_not_found_error_response = Mock(
            ok=False, status_code=404, json=Mock(return_value='payload'))
        self.fake_wait_exceeded_response = Mock(
            ok=False, status_code=408, json=Mock(return_value='payload'))
        self.fake_conflict_error_response = Mock(
            ok=False, status_code=409, json=Mock(return_value='payload'))
        self.fake_server_error_response = Mock(
            ok=False, status_code=500, json=Mock(return_value='payload'))
        self.fake_connection_error_response = Mock(
            ok=False, status_code=503, json=Mock(return_value='payload'))

    @patch('brewtils.rest.client.RestClient.get_config', Mock())
    def test_can_connect_success(self):
        self.assertTrue(self.client.can_connect())

    @patch('brewtils.rest.client.RestClient.get_config')
    def test_can_connect_failure(self, get_mock):
        get_mock.side_effect = requests.exceptions.ConnectionError
        self.assertFalse(self.client.can_connect())

    @patch('brewtils.rest.client.RestClient.get_config')
    def test_can_connect_error(self, get_mock):
        get_mock.side_effect = requests.exceptions.SSLError
        self.assertRaises(requests.exceptions.SSLError,
                          self.client.can_connect)

    @patch('brewtils.rest.client.RestClient.get_version')
    def test_get_version(self, mock_get):
        mock_get.return_value = self.fake_success_response

        self.assertEqual(self.fake_success_response, self.client.get_version())
        mock_get.assert_called()

    @patch('brewtils.rest.client.RestClient.get_version')
    def test_get_version_error(self, mock_get):
        mock_get.return_value = self.fake_server_error_response

        self.assertRaises(FetchError, self.client.get_version)
        mock_get.assert_called()

    @patch('brewtils.rest.client.RestClient.get_version')
    def test_get_version_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.get_version)

    @patch('brewtils.rest.client.RestClient.get_logging_config')
    def test_get_logging_config(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.parser.parse_logging_config = Mock(return_value='logging_config')

        self.assertEqual('logging_config',
                         self.client.get_logging_config('system_name'))
        self.parser.parse_logging_config.assert_called_with('payload')
        mock_get.assert_called()

    @patch('brewtils.rest.client.RestClient.get_logging_config')
    def test_get_logging_config_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.get_logging_config,
                          'system_name')

    # Find systems
    @patch('brewtils.rest.client.RestClient.get_systems')
    def test_find_systems_call_get_systems(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.client.find_systems()
        mock_get.assert_called()

    @patch('brewtils.rest.client.RestClient.get_systems')
    def test_find_systems_with_params_get_systems(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.client.find_systems(name='foo')
        mock_get.assert_called_with(name='foo')

    @patch('brewtils.rest.client.RestClient.get_systems')
    def test_find_systems_server_error(self, mock_get):
        mock_get.return_value = self.fake_server_error_response
        self.assertRaises(FetchError, self.client.find_systems)

    @patch('brewtils.rest.client.RestClient.get_systems')
    def test_find_systems_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.find_systems)

    @patch('brewtils.rest.client.RestClient.get_systems')
    def test_find_systems_call_parser(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.client.find_systems()
        self.parser.parse_system.assert_called_with('payload', many=True)

    @patch('brewtils.rest.easy_client.EasyClient._find_system_by_id')
    def test_find_unique_system_by_id(self, find_mock):
        system_mock = Mock()
        find_mock.return_value = system_mock

        self.assertEqual(system_mock, self.client.find_unique_system(id='id'))
        find_mock.assert_called_with('id')

    def test_find_unique_system_none(self):
        self.client.find_systems = Mock(return_value=None)
        self.assertIsNone(self.client.find_unique_system())

    def test_find_unique_system_one(self):
        self.client.find_systems = Mock(return_value=['system1'])
        self.assertEqual('system1', self.client.find_unique_system())

    def test_find_unique_system_multiple(self):
        self.client.find_systems = Mock(return_value=['system1', 'system2'])
        self.assertRaises(FetchError, self.client.find_unique_system)

    @patch('brewtils.rest.client.RestClient.get_system')
    def test_find_system_by_id(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.parser.parse_system = Mock(return_value='system')

        self.assertEqual(self.client._find_system_by_id('id', foo='bar'),
                         'system')
        self.parser.parse_system.assert_called_with('payload')
        mock_get.assert_called_with('id', foo='bar')

    @patch('brewtils.rest.client.RestClient.get_system')
    def test_find_system_by_id_404(self, mock_get):
        mock_get.return_value = self.fake_not_found_error_response

        self.assertIsNone(self.client._find_system_by_id('id', foo='bar'))
        mock_get.assert_called_with('id', foo='bar')

    @patch('brewtils.rest.client.RestClient.get_system')
    def test_find_system_by_id_server_error(self, mock_get):
        mock_get.return_value = self.fake_server_error_response

        self.assertRaises(FetchError, self.client._find_system_by_id, 'id')
        mock_get.assert_called_with('id')

    @patch('brewtils.rest.client.RestClient.get_system')
    def test_find_system_by_id_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client._find_system_by_id,
                          'id')

    # Create system
    @patch('brewtils.rest.client.RestClient.post_systems')
    def test_create_system(self, mock_post):
        mock_post.return_value = self.fake_success_response
        self.parser.serialize_system = Mock(return_value='json_system')
        self.parser.parse_system = Mock(return_value='system_response')

        self.assertEqual('system_response',
                         self.client.create_system('system'))
        self.parser.serialize_system.assert_called_with('system')
        self.parser.parse_system.assert_called_with('payload')

    @patch('brewtils.rest.client.RestClient.post_systems')
    def test_create_system_client_error(self, mock_post):
        mock_post.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.create_system, 'system')

    @patch('brewtils.rest.client.RestClient.post_systems')
    def test_create_system_server_error(self, mock_post):
        mock_post.return_value = self.fake_server_error_response
        self.assertRaises(SaveError, self.client.create_system, 'system')

    @patch('brewtils.rest.client.RestClient.post_systems')
    def test_create_system_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.create_system,
                          'system')

    # Update request
    @patch('brewtils.rest.client.RestClient.patch_system')
    def test_update_system(self, mock_patch):
        mock_patch.return_value = self.fake_success_response
        self.parser.serialize_command = Mock(return_value='new_commands')

        self.client.update_system('id', new_commands='new_commands')
        self.parser.parse_system.assert_called_with('payload')
        self.assertEqual(1, mock_patch.call_count)
        payload = mock_patch.call_args[0][1]
        self.assertNotEqual(-1, payload.find('new_commands'))

    @patch('brewtils.rest.easy_client.PatchOperation')
    @patch('brewtils.rest.client.RestClient.patch_system')
    def test_update_system_metadata(self, mock_patch, MockPatch):
        MockPatch.return_value = "patch"
        mock_patch.return_value = self.fake_success_response
        metadata = {"foo": "bar"}

        self.client.update_system('id', new_commands=None, metadata=metadata)
        MockPatch.assert_called_with('update', '/metadata', {"foo": "bar"})
        self.parser.serialize_patch.assert_called_with(["patch"], many=True)
        self.parser.parse_system.assert_called_with('payload')

    @patch('brewtils.rest.easy_client.PatchOperation')
    @patch('brewtils.rest.client.RestClient.patch_system')
    def test_update_system_kwargs(self, mock_patch, MockPatch):
        MockPatch.return_value = "patch"
        mock_patch.return_value = self.fake_success_response

        self.client.update_system('id', new_commands=None, display_name="foo")
        MockPatch.assert_called_with('replace', '/display_name', "foo")
        self.parser.serialize_patch.assert_called_with(["patch"], many=True)
        self.parser.parse_system.assert_called_with('payload')

    @patch('brewtils.rest.client.RestClient.patch_system')
    def test_update_system_client_error(self, mock_patch):
        mock_patch.return_value = self.fake_client_error_response

        self.assertRaises(ValidationError, self.client.update_system, 'id')
        mock_patch.assert_called_once_with('id', ANY)

    @patch('brewtils.rest.client.RestClient.patch_system')
    def test_update_system_invalid_id(self, mock_patch):
        mock_patch.return_value = self.fake_not_found_error_response

        self.assertRaises(NotFoundError, self.client.update_system, 'id')
        mock_patch.assert_called_once_with('id', ANY)

    @patch('brewtils.rest.client.RestClient.patch_system')
    def test_update_system_conflict(self, mock_patch):
        mock_patch.return_value = self.fake_conflict_error_response

        self.assertRaises(ConflictError, self.client.update_system, 'id')
        mock_patch.assert_called_once_with('id', ANY)

    @patch('brewtils.rest.client.RestClient.patch_system')
    def test_update_system_server_error(self, mock_patch):
        mock_patch.return_value = self.fake_server_error_response

        self.assertRaises(SaveError, self.client.update_system, 'id')
        mock_patch.assert_called_once_with('id', ANY)

    @patch('brewtils.rest.client.RestClient.patch_system')
    def test_update_system_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.update_system,
                          'system')

    # Remove system
    @patch('brewtils.rest.easy_client.EasyClient._remove_system_by_id')
    @patch('brewtils.rest.easy_client.EasyClient.find_unique_system')
    def test_remove_system(self, find_mock, remove_mock):
        find_mock.return_value = System(id='id')
        remove_mock.return_value = 'delete_response'

        self.assertEqual('delete_response',
                         self.client.remove_system(search='search params'))
        find_mock.assert_called_once_with(search='search params')
        remove_mock.assert_called_once_with('id')

    @patch('brewtils.rest.easy_client.EasyClient._remove_system_by_id')
    @patch('brewtils.rest.easy_client.EasyClient.find_unique_system')
    def test_remove_system_none_found(self, find_mock, remove_mock):
        find_mock.return_value = None

        self.assertRaises(FetchError,
                          self.client.remove_system,
                          search='search params')
        self.assertFalse(remove_mock.called)
        find_mock.assert_called_once_with(search='search params')

    @patch('brewtils.rest.client.RestClient.delete_system')
    def test_remove_system_by_id(self, mock_delete):
        mock_delete.return_value = self.fake_success_response

        self.assertTrue(self.client._remove_system_by_id('foo'))
        mock_delete.assert_called_with('foo')

    @patch('brewtils.rest.client.RestClient.delete_system')
    def test_remove_system_by_id_client_error(self, mock_remove):
        mock_remove.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client._remove_system_by_id,
                          'foo')

    @patch('brewtils.rest.client.RestClient.delete_system')
    def test_remove_system_by_id_server_error(self, mock_remove):
        mock_remove.return_value = self.fake_server_error_response
        self.assertRaises(DeleteError, self.client._remove_system_by_id, 'foo')

    @patch('brewtils.rest.client.RestClient.delete_system')
    def test_remove_system_by_id_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError,
                          self.client._remove_system_by_id, 'foo')

    def test_remove_system_by_id_none(self):
        self.assertRaises(DeleteError, self.client._remove_system_by_id, None)

    # Initialize instance
    @patch('brewtils.rest.client.RestClient.patch_instance')
    def test_initialize_instance(self, request_mock):
        request_mock.return_value = self.fake_success_response

        self.client.initialize_instance('id')
        self.assertTrue(self.parser.parse_instance.called)
        request_mock.assert_called_once_with('id', ANY)

    @patch('brewtils.rest.client.RestClient.patch_instance')
    def test_initialize_instance_client_error(self, request_mock):
        request_mock.return_value = self.fake_client_error_response

        self.assertRaises(ValidationError, self.client.initialize_instance,
                          'id')
        self.assertFalse(self.parser.parse_instance.called)
        request_mock.assert_called_once_with('id', ANY)

    @patch('brewtils.rest.client.RestClient.patch_instance')
    def test_initialize_instance_server_error(self, request_mock):
        request_mock.return_value = self.fake_server_error_response

        self.assertRaises(SaveError, self.client.initialize_instance, 'id')
        self.assertFalse(self.parser.parse_instance.called)
        request_mock.assert_called_once_with('id', ANY)

    @patch('brewtils.rest.client.RestClient.patch_instance')
    def test_initialize_instance_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.initialize_instance,
                          'id')

    @patch('brewtils.rest.client.RestClient.get_instance')
    def test_get_instance_status(self, request_mock):
        request_mock.return_value = self.fake_success_response

        self.client.get_instance_status('id')
        self.assertTrue(self.parser.parse_instance.called)
        request_mock.assert_called_once_with('id')

    @patch('brewtils.rest.client.RestClient.get_instance')
    def test_get_instance_status_client_error(self, request_mock):
        request_mock.return_value = self.fake_client_error_response

        self.assertRaises(ValidationError, self.client.get_instance_status,
                          'id')
        self.assertFalse(self.parser.parse_instance.called)
        request_mock.assert_called_once_with('id')

    @patch('brewtils.rest.client.RestClient.get_instance')
    def test_get_instance_status_server_error(self, request_mock):
        request_mock.return_value = self.fake_server_error_response

        self.assertRaises(FetchError, self.client.get_instance_status, 'id')
        self.assertFalse(self.parser.parse_instance.called)
        request_mock.assert_called_once_with('id')

    @patch('brewtils.rest.client.RestClient.get_instance')
    def test_get_instance_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.get_instance_status,
                          'id')

    @patch('brewtils.rest.client.RestClient.patch_instance')
    def test_update_instance_status(self, request_mock):
        request_mock.return_value = self.fake_success_response

        self.client.update_instance_status('id', 'status')
        self.assertTrue(self.parser.parse_instance.called)
        request_mock.assert_called_once_with('id', ANY)

    @patch('brewtils.rest.client.RestClient.patch_instance')
    def test_update_instance_status_client_error(self, request_mock):
        request_mock.return_value = self.fake_client_error_response

        self.assertRaises(ValidationError, self.client.update_instance_status,
                          'id', 'status')
        self.assertFalse(self.parser.parse_instance.called)
        request_mock.assert_called_once_with('id', ANY)

    @patch('brewtils.rest.client.RestClient.patch_instance')
    def test_update_instance_status_server_error(self, request_mock):
        request_mock.return_value = self.fake_server_error_response

        self.assertRaises(SaveError, self.client.update_instance_status, 'id',
                          'status')
        self.assertFalse(self.parser.parse_instance.called)
        request_mock.assert_called_once_with('id', ANY)

    @patch('brewtils.rest.client.RestClient.patch_instance')
    def test_update_instance_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError,
                          self.client.update_instance_status, 'id', 'status')

    # Instance heartbeat
    @patch('brewtils.rest.client.RestClient.patch_instance')
    def test_instance_heartbeat(self, request_mock):
        request_mock.return_value = self.fake_success_response

        self.assertTrue(self.client.instance_heartbeat('id'))
        request_mock.assert_called_once_with('id', ANY)

    @patch('brewtils.rest.client.RestClient.patch_instance')
    def test_instance_heartbeat_client_error(self, request_mock):
        request_mock.return_value = self.fake_client_error_response

        self.assertRaises(ValidationError, self.client.instance_heartbeat,
                          'id')
        request_mock.assert_called_once_with('id', ANY)

    @patch('brewtils.rest.client.RestClient.patch_instance')
    def test_instance_heartbeat_server_error(self, request_mock):
        request_mock.return_value = self.fake_server_error_response

        self.assertRaises(SaveError, self.client.instance_heartbeat, 'id')
        request_mock.assert_called_once_with('id', ANY)

    @patch('brewtils.rest.client.RestClient.patch_instance')
    def test_instance_heartbeat_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.instance_heartbeat,
                          'id')

    @patch('brewtils.rest.client.RestClient.delete_instance')
    def test_remove_instance(self, mock_delete):
        mock_delete.return_value = self.fake_success_response

        self.assertTrue(self.client.remove_instance('foo'))
        mock_delete.assert_called_with('foo')

    @patch('brewtils.rest.client.RestClient.delete_instance')
    def test_remove_instance_client_error(self, mock_remove):
        mock_remove.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.remove_instance, 'foo')

    @patch('brewtils.rest.client.RestClient.delete_instance')
    def test_remove_instance_server_error(self, mock_remove):
        mock_remove.return_value = self.fake_server_error_response
        self.assertRaises(DeleteError, self.client.remove_instance, 'foo')

    @patch('brewtils.rest.client.RestClient.delete_instance')
    def test_remove_instance_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.remove_instance,
                          'foo')

    def test_remove_instance_none(self):
        self.assertRaises(DeleteError, self.client.remove_instance, None)

    # Find requests
    @patch('brewtils.rest.easy_client.EasyClient._find_request_by_id')
    def test_find_unique_request_by_id(self, find_mock):
        self.client.find_unique_request(id='id')
        find_mock.assert_called_with('id')

    def test_find_unique_request_none(self):
        self.client.find_requests = Mock(return_value=None)
        self.assertIsNone(self.client.find_unique_request())

    def test_find_unique_request_one(self):
        self.client.find_requests = Mock(return_value=['request1'])
        self.assertEqual('request1', self.client.find_unique_request())

    def test_find_unique_request_multiple(self):
        self.client.find_requests = Mock(return_value=['request1', 'request2'])
        self.assertRaises(FetchError, self.client.find_unique_request)

    @patch('brewtils.rest.client.RestClient.get_requests')
    def test_find_requests(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.parser.parse_request = Mock(return_value='request')

        self.assertEqual('request', self.client.find_requests(search='params'))
        self.parser.parse_request.assert_called_with('payload', many=True)
        mock_get.assert_called_with(search='params')

    @patch('brewtils.rest.client.RestClient.get_requests')
    def test_find_requests_error(self, mock_get):
        mock_get.return_value = self.fake_server_error_response

        self.assertRaises(FetchError,
                          self.client.find_requests,
                          search='params')
        mock_get.assert_called_with(search='params')

    @patch('brewtils.rest.client.RestClient.get_requests')
    def test_find_requests_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError,
                          self.client.find_requests,
                          search='params')

    @patch('brewtils.rest.client.RestClient.get_request')
    def test_find_request_by_id(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.parser.parse_request = Mock(return_value='request')

        self.assertEqual(self.client._find_request_by_id('id'), 'request')
        self.parser.parse_request.assert_called_with('payload')
        mock_get.assert_called_with('id')

    @patch('brewtils.rest.client.RestClient.get_request')
    def test_find_request_by_id_404(self, mock_get):
        mock_get.return_value = self.fake_not_found_error_response

        self.assertIsNone(self.client._find_request_by_id('id'))
        mock_get.assert_called_with('id')

    @patch('brewtils.rest.client.RestClient.get_request')
    def test_find_request_by_id_server_error(self, mock_get):
        mock_get.return_value = self.fake_server_error_response

        self.assertRaises(FetchError, self.client._find_request_by_id, 'id')
        mock_get.assert_called_with('id')

    @patch('brewtils.rest.client.RestClient.get_request')
    def test_find_request_by_id_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client._find_request_by_id,
                          'id')

    # Create request
    @patch('brewtils.rest.client.RestClient.post_requests')
    def test_create_request(self, mock_post):
        mock_post.return_value = self.fake_success_response
        self.parser.serialize_request = Mock(return_value='json_request')
        self.parser.parse_request = Mock(return_value='request_response')

        self.assertEqual('request_response',
                         self.client.create_request('request'))
        self.parser.serialize_request.assert_called_with('request')
        self.parser.parse_request.assert_called_with('payload')

    @patch('brewtils.rest.client.RestClient.post_requests')
    def test_create_request_errors(self, mock_post):
        mock_post.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.create_request,
                          'request')

        mock_post.return_value = self.fake_wait_exceeded_response
        self.assertRaises(WaitExceededError, self.client.create_request,
                          'request')

        mock_post.return_value = self.fake_server_error_response
        self.assertRaises(SaveError, self.client.create_request, 'request')

        mock_post.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.create_request,
                          'request')

    # Update request
    @patch('brewtils.rest.client.RestClient.patch_request')
    def test_update_request(self, request_mock):
        request_mock.return_value = self.fake_success_response

        self.client.update_request('id',
                                   status='new_status',
                                   output='new_output',
                                   error_class='ValueError')
        self.parser.parse_request.assert_called_with('payload')
        self.assertEqual(1, request_mock.call_count)
        payload = request_mock.call_args[0][1]
        self.assertNotEqual(-1, payload.find('new_status'))
        self.assertNotEqual(-1, payload.find('new_output'))
        self.assertNotEqual(-1, payload.find('ValueError'))

    @patch('brewtils.rest.client.RestClient.patch_request')
    def test_update_request_client_error(self, request_mock):
        request_mock.return_value = self.fake_client_error_response

        self.assertRaises(ValidationError, self.client.update_request, 'id')
        request_mock.assert_called_once_with('id', ANY)

    @patch('brewtils.rest.client.RestClient.patch_request')
    def test_update_request_server_error(self, request_mock):
        request_mock.return_value = self.fake_server_error_response

        self.assertRaises(SaveError, self.client.update_request, 'id')
        request_mock.assert_called_once_with('id', ANY)

    @patch('brewtils.rest.client.RestClient.patch_request')
    def test_update_request_connection_error(self, request_mock):
        request_mock.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.update_request,
                          'id')

    # Publish Event
    @patch('brewtils.rest.client.RestClient.post_event')
    def test_publish_event(self, mock_post):
        mock_post.return_value = self.fake_success_response
        self.assertTrue(self.client.publish_event(Mock()))

    @patch('brewtils.rest.client.RestClient.post_event')
    def test_publish_event_errors(self, mock_post):
        mock_post.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.publish_event, 'system')

        mock_post.return_value = self.fake_server_error_response
        self.assertRaises(RestError, self.client.publish_event, 'system')

        mock_post.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.publish_event,
                          'system')

    # Queues
    @patch('brewtils.rest.client.RestClient.get_queues')
    def test_get_queues(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.client.get_queues()
        self.assertTrue(self.parser.parse_queue.called)

    @patch('brewtils.rest.client.RestClient.get_queues')
    def test_get_queues_errors(self, mock_get):
        mock_get.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.get_queues)

        mock_get.return_value = self.fake_server_error_response
        self.assertRaises(RestError, self.client.get_queues)

        mock_get.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.get_queues)

    @patch('brewtils.rest.client.RestClient.delete_queue')
    def test_clear_queue(self, mock_delete):
        mock_delete.return_value = self.fake_success_response
        self.assertTrue(self.client.clear_queue('queue'))

    @patch('brewtils.rest.client.RestClient.delete_queue')
    def test_clear_queue_errors(self, mock_delete):
        mock_delete.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.clear_queue, 'queue')

        mock_delete.return_value = self.fake_server_error_response
        self.assertRaises(RestError, self.client.clear_queue, 'queue')

        mock_delete.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.clear_queue,
                          'queue')

    @patch('brewtils.rest.client.RestClient.delete_queues')
    def test_clear_all_queues(self, mock_delete):
        mock_delete.return_value = self.fake_success_response
        self.assertTrue(self.client.clear_all_queues())

    @patch('brewtils.rest.client.RestClient.delete_queues')
    def test_clear_all_queues_errors(self, mock_delete):
        mock_delete.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.clear_all_queues)

        mock_delete.return_value = self.fake_server_error_response
        self.assertRaises(RestError, self.client.clear_all_queues)

        mock_delete.return_value = self.fake_connection_error_response
        self.assertRaises(RestConnectionError, self.client.clear_all_queues)

    # Find Jobs
    @patch('brewtils.rest.client.RestClient.get_jobs')
    def test_find_jobs(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.parser.parse_job = Mock(return_value='job')

        self.assertEqual('job', self.client.find_jobs(search='params'))
        self.parser.parse_job.assert_called_with('payload', many=True)
        mock_get.assert_called_with(search='params')

    @patch('brewtils.rest.client.RestClient.get_jobs')
    def test_find_jobs_error(self, mock_get):
        mock_get.return_value = self.fake_server_error_response

        self.assertRaises(FetchError, self.client.find_jobs, search='params')
        mock_get.assert_called_with(search='params')

    # Create Jobs
    @patch('brewtils.rest.client.RestClient.post_jobs')
    def test_create_job(self, mock_post):
        mock_post.return_value = self.fake_success_response
        self.parser.serialize_job = Mock(return_value='json_job')
        self.parser.parse_job = Mock(return_value='job_response')

        self.assertEqual('job_response', self.client.create_job('job'))
        self.parser.serialize_job.assert_called_with('job')
        self.parser.parse_job.assert_called_with('payload')

    @patch('brewtils.rest.client.RestClient.post_jobs')
    def test_create_job_error(self, mock_post):
        mock_post.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.create_job, 'job')

    # Remove Job
    @patch('brewtils.rest.client.RestClient.delete_job')
    def test_delete_job(self, mock_delete):
        mock_delete.return_value = self.fake_success_response
        self.assertEqual(True, self.client.remove_job('job_id'))

    @patch('brewtils.rest.client.RestClient.delete_job')
    def test_delete_job_error(self, mock_delete):
        mock_delete.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.remove_job, 'job_id')

    # Pause Job
    @patch('brewtils.rest.easy_client.PatchOperation')
    @patch('brewtils.rest.client.RestClient.patch_job')
    def test_pause_job(self, mock_patch, MockPatch):
        MockPatch.return_value = "patch"
        mock_patch.return_value = self.fake_success_response

        self.client.pause_job('id')
        MockPatch.assert_called_with('update', '/status', 'PAUSED')
        self.parser.serialize_patch.assert_called_with(["patch"], many=True)
        self.parser.parse_job.assert_called_with('payload')

    @patch('brewtils.rest.client.RestClient.patch_job')
    def test_pause_job_error(self, mock_patch):
        mock_patch.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.pause_job, 'id')

    @patch('brewtils.rest.easy_client.PatchOperation')
    @patch('brewtils.rest.client.RestClient.patch_job')
    def test_resume_job(self, mock_patch, MockPatch):
        MockPatch.return_value = "patch"
        mock_patch.return_value = self.fake_success_response

        self.client.resume_job('id')
        MockPatch.assert_called_with('update', '/status', 'RUNNING')
        self.parser.serialize_patch.assert_called_with(["patch"], many=True)
        self.parser.parse_job.assert_called_with('payload')

    # Users
    @patch('brewtils.rest.client.RestClient.get_user')
    def test_who_am_i(self, mock_get):
        self.client.who_am_i()
        mock_get.assert_called_with('anonymous')

    @patch('brewtils.rest.client.RestClient.get_user')
    def test_get_user(self, mock_get):
        mock_get.return_value = self.fake_success_response
        self.client.get_user('identifier')
        self.assertTrue(self.parser.parse_principal.called)

    @patch('brewtils.rest.client.RestClient.get_user')
    def test_get_user_errors(self, mock_get):
        mock_get.return_value = self.fake_client_error_response
        self.assertRaises(ValidationError, self.client.get_user, 'identifier')

        mock_get.return_value = self.fake_not_found_error_response
        self.assertRaises(NotFoundError, self.client.get_user, 'identifier')