Skip to content

Commit

Permalink
Suppress most cli errors, but still warn if we get too many (#416)
Browse files Browse the repository at this point in the history
* Suppress most cli errors, but still warn if we get too many

* Add a config option for setting the error threshold

* Add some clarification about config resolution order

* Fix typo in docs/reference/cli-config.rst

Co-authored-by: tang-mm <3333407+tang-mm@users.noreply.github.com>

* Workaround strange pylint issue on github

---------

Co-authored-by: tang-mm <3333407+tang-mm@users.noreply.github.com>
  • Loading branch information
plars and tang-mm authored Jan 23, 2025
1 parent 3d4b851 commit 4ba3eee
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 15 deletions.
3 changes: 2 additions & 1 deletion cli/.pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ good-names = k, v
[MESSAGES CONTROL]
# We currently have some older systems running this which
# don't support f-strings yet
disable = C0209
# Disable too-many-lines check because pylint on github seems to be miscounting lines in __init__.py
disable = C0209,too-many-lines
18 changes: 14 additions & 4 deletions cli/testflinger_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ def __init__(self):
or self.config.get("secret_key")
or os.environ.get("TESTFLINGER_SECRET_KEY")
)
error_threshold = self.config.get(
"error_threshold"
or os.environ.get("TESTFLINGER_ERROR_THRESHOLD")
or 3
)

# Allow config subcommand without worrying about server or client
if (
Expand All @@ -168,7 +173,7 @@ def __init__(self):
'Server must start with "http://" or "https://" '
'- currently set to: "{}"'.format(server)
)
self.client = client.Client(server)
self.client = client.Client(server, error_threshold=error_threshold)

def run(self):
"""Run the subcommand specified in command line arguments"""
Expand Down Expand Up @@ -367,7 +372,12 @@ def status(self):
job_state = self.get_job_state(self.args.job_id)
if job_state != "unknown":
self.history.update(self.args.job_id, job_state)
print(job_state)
print(job_state)
else:
print(
"Unable to retrieve job state from the server, check your "
"connection or try again later."
)

def cancel(self, job_id=None):
"""Tell the server to cancel a specified JOB_ID"""
Expand Down Expand Up @@ -988,8 +998,8 @@ def get_job_state(self, job_id):
"Received 404 error from server. Are you "
"sure this is a testflinger server?"
)
except (IOError, ValueError):
except (IOError, ValueError) as exc:
# For other types of network errors, or JSONDecodeError if we got
# a bad return from get_status()
logger.warning("Unable to retrieve job state.")
logger.debug("Unable to retrieve job state: %s", exc)
return "unknown"
22 changes: 13 additions & 9 deletions cli/testflinger_cli/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ def __init__(self, status, msg=""):
class Client:
"""Testflinger connection client"""

def __init__(self, server):
def __init__(self, server, error_threshold=3):
self.server = server
self.error_count = 0
self.error_threshold = error_threshold

def get(self, uri_frag, timeout=15, headers=None):
"""Submit a GET request to the server
Expand All @@ -56,14 +58,16 @@ def get(self, uri_frag, timeout=15, headers=None):
uri = urllib.parse.urljoin(self.server, uri_frag)
try:
req = requests.get(uri, timeout=timeout, headers=headers)
except requests.exceptions.ConnectionError:
logger.error("Unable to communicate with specified server.")
raise
except IOError:
# This should catch all other timeout cases
logger.error(
"Timeout while trying to communicate with the server."
)
self.error_count = 0
except (IOError, requests.exceptions.ConnectionError) as exc:
self.error_count += 1
if self.error_count % self.error_threshold == 0:
logger.warning(
"Error communicating with the server for the past %s "
"requests, but will continue to retry. Last error: %s",
self.error_count,
exc,
)
raise
if req.status_code != 200:
raise HTTPError(req.status_code)
Expand Down
47 changes: 47 additions & 0 deletions cli/testflinger_cli/tests/test_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright (C) 2024 Canonical
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

"""
Unit tests for the Client class
"""

import logging
import pytest
import requests

from testflinger_cli.client import Client


def test_get_error_threshold(caplog, requests_mock):
"""Test that a warning is logged when error_threshold is reached"""
caplog.set_level(logging.WARNING)
requests_mock.get(
"http://testflinger/test", exc=requests.exceptions.ConnectionError
)
client = Client("http://testflinger", error_threshold=3)
for _ in range(2):
with pytest.raises(requests.exceptions.ConnectionError):
client.get("test")
assert (
"Error communicating with the server for the past"
not in caplog.text
)
with pytest.raises(requests.exceptions.ConnectionError):
client.get("test")
assert (
"Error communicating with the server for the past 3 requests"
in caplog.text
)
47 changes: 47 additions & 0 deletions docs/reference/cli-config.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
Command Line Interface Configuration
====================================

The Testflinger CLI can be configured on the command line using the parameters described in ``testflinger --help``. However, sometimes it also supports using environment variables, or reading values from a configuration file. This can be helpful for CI/CD environments, or for setting up a config with different default values read from a config file.

The configuration file is read from ``$XDG_CONFIG_HOME/testflinger-cli.conf`` by default, but this can be overridden with the ``--configfile`` parameter.

When a configuration variable is defined in multiple locations, Testflinger resolves the value by applying the following priority order, from highest to lowest:

1. command-line parameter (for example, ``--server <local_server>``)

2. configuration file

3. environment variable

You can show the current values stored in the config file by running ``testflinger config``. If no value is found on the command line, config file, or environment variable, then a safe default value will be used.

To set a configuration value in the config file, you can edit it directly or use ``testflinger config key=value``.

Testflinger Config Variables
----------------------------

The following config values are currently supported:

* server - Testflinger Server URL to use

* Config file key: ``server``
* Environment variable: ``TESTFLINGER_SERVER``
* Default: ``https://testflinger.canonical.com``

* error_threshold - warn the user of possible server/network problems after failing to read from the server after this many consecutive attempts

* Config file key: ``error_threshold``
* Environment variable: ``TESTFLINGER_ERROR_THRESHOLD``
* Default: ``3``

* client_id - Client ID to use for APIs that require authorisation

* Config file key: ``client_id``
* Environment variable: ``TESTFLINGER_CLIENT_ID``
* Default: ``None``

* secret_key - Secret key to use for APIs that require authorisation

* Config file key: ``secret_key``
* Environment variable: ``TESTFLINGER_SECRET_KEY``
* Default: ``None``
10 changes: 9 additions & 1 deletion docs/reference/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,12 @@ Working with Testflinger servers and agents

testflinger-server-conf
testflinger-agent-conf
test-phases
test-phases

Using the Command Line Interface
--------------------------------

.. toctree::
:maxdepth: 1

cli-config

0 comments on commit 4ba3eee

Please sign in to comment.