mirror of
https://github.com/saltstack/salt.git
synced 2025-04-16 09:40:20 +00:00
Fix authentication error handling and incomplete error messages in proxmox salt-cloud driver
Fixes #62211
This commit is contained in:
parent
7ce54c9c67
commit
0121e725bb
3 changed files with 118 additions and 4 deletions
1
changelog/62211.fixed
Normal file
1
changelog/62211.fixed
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Improved error handling and diagnostics in the proxmox salt-cloud driver
|
|
@ -136,7 +136,9 @@ def _authenticate():
|
||||||
connect_data = {"username": username, "password": passwd}
|
connect_data = {"username": username, "password": passwd}
|
||||||
full_url = "https://{}:{}/api2/json/access/ticket".format(url, port)
|
full_url = "https://{}:{}/api2/json/access/ticket".format(url, port)
|
||||||
|
|
||||||
returned_data = requests.post(full_url, verify=verify_ssl, data=connect_data).json()
|
response = requests.post(full_url, verify=verify_ssl, data=connect_data)
|
||||||
|
response.raise_for_status()
|
||||||
|
returned_data = response.json()
|
||||||
|
|
||||||
ticket = {"PVEAuthCookie": returned_data["data"]["ticket"]}
|
ticket = {"PVEAuthCookie": returned_data["data"]["ticket"]}
|
||||||
csrf = str(returned_data["data"]["CSRFPreventionToken"])
|
csrf = str(returned_data["data"]["CSRFPreventionToken"])
|
||||||
|
@ -655,12 +657,18 @@ def create(vm_):
|
||||||
newid = _get_next_vmid()
|
newid = _get_next_vmid()
|
||||||
data = create_node(vm_, newid)
|
data = create_node(vm_, newid)
|
||||||
except Exception as exc: # pylint: disable=broad-except
|
except Exception as exc: # pylint: disable=broad-except
|
||||||
|
msg = str(exc)
|
||||||
|
if (
|
||||||
|
isinstance(exc, requests.exceptions.RequestException)
|
||||||
|
and exc.response is not None
|
||||||
|
):
|
||||||
|
msg = msg + "\n" + exc.response.text
|
||||||
log.error(
|
log.error(
|
||||||
"Error creating %s on PROXMOX\n\n"
|
"Error creating %s on PROXMOX\n\n"
|
||||||
"The following exception was thrown when trying to "
|
"The following exception was thrown when trying to "
|
||||||
"run the initial deployment: \n%s",
|
"run the initial deployment: \n%s",
|
||||||
vm_["name"],
|
vm_["name"],
|
||||||
exc,
|
msg,
|
||||||
# Show the traceback if the debug logging level is enabled
|
# Show the traceback if the debug logging level is enabled
|
||||||
exc_info_on_loglevel=logging.DEBUG,
|
exc_info_on_loglevel=logging.DEBUG,
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,13 +2,35 @@
|
||||||
:codeauthor: Tyler Johnson <tjohnson@saltstack.com>
|
:codeauthor: Tyler Johnson <tjohnson@saltstack.com>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import io
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from salt import config
|
||||||
from salt.cloud.clouds import proxmox
|
from salt.cloud.clouds import proxmox
|
||||||
|
from tests.support.helpers import TstSuiteLoggingHandler
|
||||||
from tests.support.mixins import LoaderModuleMockMixin
|
from tests.support.mixins import LoaderModuleMockMixin
|
||||||
from tests.support.mock import ANY, MagicMock, patch
|
from tests.support.mock import ANY, MagicMock, patch
|
||||||
from tests.support.unit import TestCase
|
from tests.support.unit import TestCase
|
||||||
|
|
||||||
|
PROFILE = {
|
||||||
|
"my_proxmox": {
|
||||||
|
"provider": "my_proxmox",
|
||||||
|
"image": "local:some_image.tgz",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PROVIDER_CONFIG = {
|
||||||
|
"my_proxmox": {
|
||||||
|
"proxmox": {
|
||||||
|
"driver": "proxmox",
|
||||||
|
"url": "pve@domain.com",
|
||||||
|
"user": "cloud@pve",
|
||||||
|
"password": "verybadpass",
|
||||||
|
"profiles": PROFILE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ProxmoxTest(TestCase, LoaderModuleMockMixin):
|
class ProxmoxTest(TestCase, LoaderModuleMockMixin):
|
||||||
def setup_loader_modules(self):
|
def setup_loader_modules(self):
|
||||||
|
@ -22,8 +44,8 @@ class ProxmoxTest(TestCase, LoaderModuleMockMixin):
|
||||||
"__opts__": {
|
"__opts__": {
|
||||||
"sock_dir": True,
|
"sock_dir": True,
|
||||||
"transport": True,
|
"transport": True,
|
||||||
"providers": {"my_proxmox": {}},
|
"providers": PROVIDER_CONFIG,
|
||||||
"profiles": {"my_proxmox": {}},
|
"profiles": PROFILE,
|
||||||
},
|
},
|
||||||
"__active_provider_name__": "my_proxmox:proxmox",
|
"__active_provider_name__": "my_proxmox:proxmox",
|
||||||
}
|
}
|
||||||
|
@ -262,3 +284,86 @@ class ProxmoxTest(TestCase, LoaderModuleMockMixin):
|
||||||
proxmox._import_api()
|
proxmox._import_api()
|
||||||
self.assertEqual(proxmox.api, [{"info": {}}])
|
self.assertEqual(proxmox.api, [{"info": {}}])
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def test__authenticate_success(self):
|
||||||
|
response = requests.Response()
|
||||||
|
response.status_code = 200
|
||||||
|
response.reason = "OK"
|
||||||
|
response.raw = io.BytesIO(
|
||||||
|
b"""{"data":{"CSRFPreventionToken":"01234567:dG9rZW4=","ticket":"PVE:cloud@pve:01234567::dGlja2V0"}}"""
|
||||||
|
)
|
||||||
|
with patch("requests.post", return_value=response):
|
||||||
|
proxmox._authenticate()
|
||||||
|
assert proxmox.csrf and proxmox.ticket
|
||||||
|
return
|
||||||
|
|
||||||
|
def test__authenticate_failure(self):
|
||||||
|
"""
|
||||||
|
Confirm that authentication failure raises an exception.
|
||||||
|
"""
|
||||||
|
response = requests.Response()
|
||||||
|
response.status_code = 401
|
||||||
|
response.reason = "authentication failure"
|
||||||
|
response.raw = io.BytesIO(b"""{"data":null}""")
|
||||||
|
with patch("requests.post", return_value=response):
|
||||||
|
self.assertRaises(requests.exceptions.HTTPError, proxmox._authenticate)
|
||||||
|
return
|
||||||
|
|
||||||
|
def test_creation_failure_logging(self):
|
||||||
|
"""
|
||||||
|
Test detailed logging on HTTP errors during VM creation.
|
||||||
|
"""
|
||||||
|
vm_ = {
|
||||||
|
"profile": "my_proxmox",
|
||||||
|
"name": "vm4",
|
||||||
|
"technology": "lxc",
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"image": "local:some_image.tgz",
|
||||||
|
"onboot": True,
|
||||||
|
}
|
||||||
|
self.assertEqual(
|
||||||
|
config.is_profile_configured(
|
||||||
|
proxmox.__opts__, "my_proxmox:proxmox", "my_proxmox", vm_=vm_
|
||||||
|
),
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = requests.Response()
|
||||||
|
response.status_code = 400
|
||||||
|
response.reason = "Parameter verification failed."
|
||||||
|
response.raw = io.BytesIO(
|
||||||
|
b"""{"data":null,"errors":{"onboot":"type check ('boolean') failed - got 'True'"}}"""
|
||||||
|
)
|
||||||
|
|
||||||
|
def mock_query_response(conn_type, option, post_data=None):
|
||||||
|
if conn_type == "get" and option == "cluster/nextid":
|
||||||
|
return 104
|
||||||
|
if conn_type == "post" and option == "nodes/127.0.0.1/lxc":
|
||||||
|
response.raise_for_status()
|
||||||
|
return response
|
||||||
|
return None
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
proxmox, "query", side_effect=mock_query_response
|
||||||
|
), patch.object(
|
||||||
|
proxmox, "_get_properties", return_value=set()
|
||||||
|
), TstSuiteLoggingHandler() as log_handler:
|
||||||
|
self.assertEqual(proxmox.create(vm_), False)
|
||||||
|
|
||||||
|
# Search for these messages in a multi-line log entry.
|
||||||
|
missing = {
|
||||||
|
"{} Client Error: {} for url:".format(
|
||||||
|
response.status_code, response.reason
|
||||||
|
),
|
||||||
|
response.text,
|
||||||
|
}
|
||||||
|
for required in list(missing):
|
||||||
|
for message in log_handler.messages:
|
||||||
|
if required in message:
|
||||||
|
missing.remove(required)
|
||||||
|
break
|
||||||
|
if missing:
|
||||||
|
raise AssertionError(
|
||||||
|
"Did not find error messages: {}".format(sorted(list(missing)))
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
Loading…
Add table
Reference in a new issue