ubuntu-advantage: determine on error if token was invalid
In case of error, u-a-c exits with status 1. When the contract token supplied is invalid, it is also considered an error ; which previously made us unable to make the distinction between: * an invalid token * a more generic error such as network error or service unavailable error Thanks to an update in u-a-c, we can now determine if the token was invalid, by parsing the standard output of the process, even when it exits with status 1. The output is expected to be a JSON object that includes an array of errors where each error has a specific error code. The error code for an invalid token is attach-invalid-token ; which we now look for in the output to determine if the contract token was invalid. Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
This commit is contained in:
parent
0fa8c54296
commit
9d6bbf2b9c
|
@ -13,7 +13,7 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from subprocess import CalledProcessError, CompletedProcess
|
||||
from subprocess import CompletedProcess
|
||||
import unittest
|
||||
from unittest.mock import patch, AsyncMock
|
||||
|
||||
|
@ -87,9 +87,9 @@ class TestUAClientUAInterfaceStrategy(unittest.IsolatedAsyncioTestCase):
|
|||
mock_arun.return_value = CompletedProcess([], 0)
|
||||
mock_arun.return_value.stdout = "{}"
|
||||
await strategy.query_info(token="123456789")
|
||||
mock_arun.assert_called_once_with(command, check=True)
|
||||
mock_arun.assert_called_once_with(command, check=False)
|
||||
|
||||
async def test_query_info_failed(self):
|
||||
async def test_query_info_unknown_error(self):
|
||||
strategy = UAClientUAInterfaceStrategy()
|
||||
command = (
|
||||
"ubuntu-advantage",
|
||||
|
@ -99,12 +99,42 @@ class TestUAClientUAInterfaceStrategy(unittest.IsolatedAsyncioTestCase):
|
|||
)
|
||||
|
||||
with patch(self.arun_command_sym) as mock_arun:
|
||||
mock_arun.side_effect = CalledProcessError(returncode=1,
|
||||
cmd=command)
|
||||
mock_arun.return_value.returncode = 2
|
||||
mock_arun.return_value.stdout = "{}"
|
||||
with self.assertRaises(CheckSubscriptionError):
|
||||
await strategy.query_info(token="123456789")
|
||||
mock_arun.assert_called_once_with(command, check=True)
|
||||
mock_arun.assert_called_once_with(command, check=False)
|
||||
|
||||
async def test_query_info_invalid_token(self):
|
||||
strategy = UAClientUAInterfaceStrategy()
|
||||
command = (
|
||||
"ubuntu-advantage",
|
||||
"status",
|
||||
"--format", "json",
|
||||
"--simulate-with-token", "123456789",
|
||||
)
|
||||
|
||||
with patch(self.arun_command_sym) as mock_arun:
|
||||
mock_arun.return_value.returncode = 1
|
||||
mock_arun.return_value.stdout = """\
|
||||
{
|
||||
"environment_vars": [],
|
||||
"errors": [
|
||||
{
|
||||
"message": "Invalid token. See https://ubuntu.com/advantage",
|
||||
"message_code": "attach-invalid-token",
|
||||
"service": null,
|
||||
"type": "system"
|
||||
}
|
||||
],
|
||||
"result": "failure",
|
||||
"services": [],
|
||||
"warnings": []
|
||||
}
|
||||
"""
|
||||
with self.assertRaises(InvalidTokenError):
|
||||
await strategy.query_info(token="123456789")
|
||||
mock_arun.assert_called_once_with(command, check=False)
|
||||
|
||||
async def test_query_info_invalid_json(self):
|
||||
strategy = UAClientUAInterfaceStrategy()
|
||||
|
@ -120,7 +150,7 @@ class TestUAClientUAInterfaceStrategy(unittest.IsolatedAsyncioTestCase):
|
|||
mock_arun.return_value.stdout = "invalid-json"
|
||||
with self.assertRaises(CheckSubscriptionError):
|
||||
await strategy.query_info(token="123456789")
|
||||
mock_arun.assert_called_once_with(command, check=True)
|
||||
mock_arun.assert_called_once_with(command, check=False)
|
||||
|
||||
|
||||
class TestUAInterface(unittest.IsolatedAsyncioTestCase):
|
||||
|
|
|
@ -17,9 +17,10 @@ helper. """
|
|||
|
||||
from abc import ABC, abstractmethod
|
||||
from datetime import datetime as dt
|
||||
import contextlib
|
||||
import json
|
||||
import logging
|
||||
from subprocess import CalledProcessError, CompletedProcess
|
||||
from subprocess import CompletedProcess
|
||||
from typing import List, Sequence, Union
|
||||
import asyncio
|
||||
|
||||
|
@ -132,20 +133,36 @@ class UAClientUAInterfaceStrategy(UAInterfaceStrategy):
|
|||
"--format", "json",
|
||||
"--simulate-with-token", token,
|
||||
)
|
||||
try:
|
||||
proc: CompletedProcess = await utils.arun_command(command,
|
||||
check=True)
|
||||
|
||||
# On error, the command will exit with status 1. When that happens, the
|
||||
# output should still be formatted as a JSON object and we can inspect
|
||||
# it to know the reason of the failure. This is how we figure out if
|
||||
# the contract token was invalid.
|
||||
proc: CompletedProcess = await utils.arun_command(command, check=False)
|
||||
if proc.returncode == 0:
|
||||
# TODO check if we're not returning a string or a list
|
||||
try:
|
||||
return json.loads(proc.stdout)
|
||||
except CalledProcessError:
|
||||
log.exception("Failed to execute command %r", command)
|
||||
# TODO Check if the command failed because the token is invalid.
|
||||
# Currently, ubuntu-advantage fails with the following error when
|
||||
# the token is invalid:
|
||||
# * Failed to connect to authentication server
|
||||
# * Check your Internet connection and try again.
|
||||
except json.JSONDecodeError:
|
||||
log.exception("Failed to parse output of command %r", command)
|
||||
elif proc.returncode == 1:
|
||||
try:
|
||||
data = json.loads(proc.stdout)
|
||||
except json.JSONDecodeError:
|
||||
log.exception("Failed to parse output of command %r", command)
|
||||
else:
|
||||
token_invalid = False
|
||||
with contextlib.suppress(KeyError):
|
||||
for error in data["errors"]:
|
||||
if error["message_code"] == "attach-invalid-token":
|
||||
token_invalid = True
|
||||
log.debug("error reported by u-a-c: %s: %s",
|
||||
error["message_code"], error["message"])
|
||||
if token_invalid:
|
||||
raise InvalidTokenError(token)
|
||||
|
||||
else:
|
||||
log.exception("Failed to execute command %r", command)
|
||||
|
||||
message = "Unable to retrieve subscription information."
|
||||
raise CheckSubscriptionError(token, message=message)
|
||||
|
|
Loading…
Reference in New Issue