Merge "Move configuration mold utilities"
This commit is contained in:
commit
cd75c7dc70
112
ironic/common/molds.py
Normal file
112
ironic/common/molds.py
Normal file
@ -0,0 +1,112 @@
|
||||
# Copyright (c) 2021 Dell Inc. or its subsidiaries.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import base64
|
||||
import requests
|
||||
import tenacity
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import swift
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def save_configuration(task, url, data):
|
||||
"""Store configuration mold to indicated location.
|
||||
|
||||
:param task: A TaskManager instance.
|
||||
:param name: URL of the configuration item to save to.
|
||||
:param data: Content of JSON data to save.
|
||||
|
||||
:raises IronicException: If using Swift storage and no authentication
|
||||
token found in task's context.
|
||||
:raises HTTPError: If failed to complete HTTP request.
|
||||
"""
|
||||
@tenacity.retry(
|
||||
retry=tenacity.retry_if_exception_type(
|
||||
requests.exceptions.ConnectionError),
|
||||
stop=tenacity.stop_after_attempt(CONF.molds.retry_attempts),
|
||||
wait=tenacity.wait_fixed(CONF.molds.retry_interval),
|
||||
reraise=True
|
||||
)
|
||||
def _request(url, data, auth_header):
|
||||
return requests.put(
|
||||
url, data=json.dumps(data, indent=2), headers=auth_header)
|
||||
|
||||
auth_header = _get_auth_header(task)
|
||||
response = _request(url, data, auth_header)
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
def get_configuration(task, url):
|
||||
"""Gets configuration mold from indicated location.
|
||||
|
||||
:param task: A TaskManager instance.
|
||||
:param url: URL of the configuration item to get.
|
||||
|
||||
:returns: JSON configuration mold
|
||||
|
||||
:raises IronicException: If using Swift storage and no authentication
|
||||
token found in task's context.
|
||||
:raises HTTPError: If failed to complete HTTP request.
|
||||
"""
|
||||
@tenacity.retry(
|
||||
retry=tenacity.retry_if_exception_type(
|
||||
requests.exceptions.ConnectionError),
|
||||
stop=tenacity.stop_after_attempt(CONF.molds.retry_attempts),
|
||||
wait=tenacity.wait_fixed(CONF.molds.retry_interval),
|
||||
reraise=True
|
||||
)
|
||||
def _request(url, auth_header):
|
||||
return requests.get(url, headers=auth_header)
|
||||
|
||||
auth_header = _get_auth_header(task)
|
||||
response = _request(url, auth_header)
|
||||
if response.status_code == requests.codes.ok:
|
||||
return response.json()
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
def _get_auth_header(task):
|
||||
"""Based on setup of configuration mold storage gets authentication header
|
||||
|
||||
:param task: A TaskManager instance.
|
||||
:raises IronicException: If using Swift storage and no authentication
|
||||
token found in task's context.
|
||||
"""
|
||||
auth_header = None
|
||||
if CONF.molds.storage == 'swift':
|
||||
# TODO(ajya) Need to update to use Swift client and context session
|
||||
auth_token = swift.get_swift_session().get_token()
|
||||
if auth_token:
|
||||
auth_header = {'X-Auth-Token': auth_token}
|
||||
else:
|
||||
raise exception.IronicException(
|
||||
_('Missing auth_token for configuration mold access for node '
|
||||
'%s') % task.node.uuid)
|
||||
elif CONF.molds.storage == 'http':
|
||||
if CONF.molds.user and CONF.molds.password:
|
||||
auth_header = {'Authorization': 'Basic %s'
|
||||
% base64.encode_as_text(
|
||||
'%s:%s' % (CONF.molds.user,
|
||||
CONF.molds.password))}
|
||||
return auth_header
|
@ -38,6 +38,7 @@ from ironic.conf import irmc
|
||||
from ironic.conf import iscsi
|
||||
from ironic.conf import metrics
|
||||
from ironic.conf import metrics_statsd
|
||||
from ironic.conf import molds
|
||||
from ironic.conf import neutron
|
||||
from ironic.conf import nova
|
||||
from ironic.conf import pxe
|
||||
@ -72,6 +73,7 @@ iscsi.register_opts(CONF)
|
||||
anaconda.register_opts(CONF)
|
||||
metrics.register_opts(CONF)
|
||||
metrics_statsd.register_opts(CONF)
|
||||
molds.register_opts(CONF)
|
||||
neutron.register_opts(CONF)
|
||||
nova.register_opts(CONF)
|
||||
pxe.register_opts(CONF)
|
||||
|
@ -423,26 +423,6 @@ webserver_opts = [
|
||||
'with images.')),
|
||||
]
|
||||
|
||||
configuration_mold_opts = [
|
||||
cfg.StrOpt('mold_storage',
|
||||
default='swift',
|
||||
help=_('Configuration mold storage location. Supports "swift" '
|
||||
'and "http". By default "swift".')),
|
||||
cfg.StrOpt('mold_user',
|
||||
help=_('User for "http" Basic auth. By default set empty.')),
|
||||
cfg.StrOpt('mold_password',
|
||||
help=_('Password for "http" Basic auth. By default set '
|
||||
'empty.')),
|
||||
cfg.StrOpt('mold_retry_attempts',
|
||||
default=3,
|
||||
help=_('Retry attempts for saving or getting configuration '
|
||||
'molds.')),
|
||||
cfg.StrOpt('mold_retry_interval',
|
||||
default=3,
|
||||
help=_('Retry interval for saving or getting configuration '
|
||||
'molds.'))
|
||||
]
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_opts(api_opts)
|
||||
@ -458,4 +438,3 @@ def register_opts(conf):
|
||||
conf.register_opts(service_opts)
|
||||
conf.register_opts(utils_opts)
|
||||
conf.register_opts(webserver_opts)
|
||||
conf.register_opts(configuration_mold_opts)
|
||||
|
41
ironic/conf/molds.py
Normal file
41
ironic/conf/molds.py
Normal file
@ -0,0 +1,41 @@
|
||||
# Copyright (c) 2021 Dell Inc. or its subsidiaries.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from ironic.common.i18n import _
|
||||
|
||||
opts = [
|
||||
cfg.StrOpt('storage',
|
||||
default='swift',
|
||||
help=_('Configuration mold storage location. Supports "swift" '
|
||||
'and "http". By default "swift".')),
|
||||
cfg.StrOpt('user',
|
||||
help=_('User for "http" Basic auth. By default set empty.')),
|
||||
cfg.StrOpt('password',
|
||||
help=_('Password for "http" Basic auth. By default set '
|
||||
'empty.')),
|
||||
cfg.StrOpt('retry_attempts',
|
||||
default=3,
|
||||
help=_('Retry attempts for saving or getting configuration '
|
||||
'molds.')),
|
||||
cfg.StrOpt('retry_interval',
|
||||
default=3,
|
||||
help=_('Retry interval for saving or getting configuration '
|
||||
'molds.'))
|
||||
]
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
conf.register_opts(opts, group='molds')
|
@ -54,6 +54,7 @@ _opts = [
|
||||
('anaconda', ironic.conf.anaconda.opts),
|
||||
('metrics', ironic.conf.metrics.opts),
|
||||
('metrics_statsd', ironic.conf.metrics_statsd.opts),
|
||||
('molds', ironic.conf.molds.opts),
|
||||
('neutron', ironic.conf.neutron.list_opts()),
|
||||
('nova', ironic.conf.nova.list_opts()),
|
||||
('pxe', ironic.conf.pxe.opts),
|
||||
|
@ -12,7 +12,6 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
@ -21,8 +20,6 @@ from oslo_log import log as logging
|
||||
from oslo_serialization import base64
|
||||
from oslo_utils import strutils
|
||||
from oslo_utils import timeutils
|
||||
import requests
|
||||
import tenacity
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
@ -387,85 +384,3 @@ OPTIONAL_PROPERTIES = {
|
||||
"deprecated in favor of the new ones."
|
||||
"Defaults to 'Default'. Optional."),
|
||||
}
|
||||
|
||||
|
||||
def save_configuration_mold(task, url, data):
|
||||
"""Store configuration mold to indicated location.
|
||||
|
||||
:param task: A TaskManager instance.
|
||||
:param name: URL of the configuration item to save to.
|
||||
:param data: Content of JSON data to save.
|
||||
|
||||
:raises IronicException: If using Swift storage and no authentication
|
||||
token found in task's context.
|
||||
:raises HTTPError: If failed to complete HTTP request.
|
||||
"""
|
||||
@tenacity.retry(
|
||||
retry=tenacity.retry_if_exception_type(
|
||||
requests.exceptions.ConnectionError),
|
||||
stop=tenacity.stop_after_attempt(CONF.mold_retry_attempts),
|
||||
wait=tenacity.wait_fixed(CONF.mold_retry_interval),
|
||||
reraise=True
|
||||
)
|
||||
def _request(url, data, auth_header):
|
||||
return requests.put(
|
||||
url, data=json.dumps(data, indent=2), headers=auth_header)
|
||||
|
||||
auth_header = _get_auth_header(task)
|
||||
response = _request(url, data, auth_header)
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
def get_configuration_mold(task, url):
|
||||
"""Gets configuration mold from indicated location.
|
||||
|
||||
:param task: A TaskManager instance.
|
||||
:param url: URL of the configuration item to get.
|
||||
|
||||
:returns: JSON configuration mold
|
||||
|
||||
:raises IronicException: If using Swift storage and no authentication
|
||||
token found in task's context.
|
||||
:raises HTTPError: If failed to complete HTTP request.
|
||||
"""
|
||||
@tenacity.retry(
|
||||
retry=tenacity.retry_if_exception_type(
|
||||
requests.exceptions.ConnectionError),
|
||||
stop=tenacity.stop_after_attempt(CONF.mold_retry_attempts),
|
||||
wait=tenacity.wait_fixed(CONF.mold_retry_interval),
|
||||
reraise=True
|
||||
)
|
||||
def _request(url, auth_header):
|
||||
return requests.get(url, headers=auth_header)
|
||||
|
||||
auth_header = _get_auth_header(task)
|
||||
response = _request(url, auth_header)
|
||||
if response.status_code == requests.codes.ok:
|
||||
return response.json()
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
def _get_auth_header(task):
|
||||
"""Based on setup of configuration mold storage gets authentication header
|
||||
|
||||
:param task: A TaskManager instance.
|
||||
:raises IronicException: If using Swift storage and no authentication
|
||||
token found in task's context.
|
||||
"""
|
||||
auth_header = None
|
||||
if CONF.mold_storage == 'swift':
|
||||
# TODO(ajya) Need to update to use Swift client and context session
|
||||
auth_token = swift.get_swift_session().get_token()
|
||||
if auth_token:
|
||||
auth_header = {'X-Auth-Token': auth_token}
|
||||
else:
|
||||
raise exception.IronicException(
|
||||
_('Missing auth_token for configuration mold access for node '
|
||||
'%s') % task.node.uuid)
|
||||
elif CONF.mold_storage == 'http':
|
||||
if CONF.mold_user and CONF.mold_password:
|
||||
auth_header = {'Authorization': 'Basic %s'
|
||||
% base64.encode_as_text(
|
||||
'%s:%s' % (CONF.mold_user, CONF.mold_password))}
|
||||
return auth_header
|
||||
|
291
ironic/tests/unit/common/test_molds.py
Normal file
291
ironic/tests/unit/common/test_molds.py
Normal file
@ -0,0 +1,291 @@
|
||||
# Copyright (c) 2021 Dell Inc. or its subsidiaries.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
import requests
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common import molds
|
||||
from ironic.common import swift
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.tests.unit.db import base as db_base
|
||||
from ironic.tests.unit.objects import utils as obj_utils
|
||||
|
||||
|
||||
class ConfigurationMoldTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ConfigurationMoldTestCase, self).setUp()
|
||||
self.node = obj_utils.create_test_node(self.context)
|
||||
|
||||
@mock.patch.object(swift, 'get_swift_session', autospec=True)
|
||||
@mock.patch.object(requests, 'put', autospec=True)
|
||||
def test_save_configuration_swift(self, mock_put, mock_swift):
|
||||
mock_session = mock.Mock()
|
||||
mock_session.get_token.return_value = 'token'
|
||||
mock_swift.return_value = mock_session
|
||||
cfg.CONF.molds.storage = 'swift'
|
||||
url = 'https://example.com/file1'
|
||||
data = {'key': 'value'}
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
molds.save_configuration(task, url, data)
|
||||
|
||||
mock_put.assert_called_once_with(url, '{\n "key": "value"\n}',
|
||||
headers={'X-Auth-Token': 'token'})
|
||||
|
||||
@mock.patch.object(swift, 'get_swift_session', autospec=True)
|
||||
@mock.patch.object(requests, 'put', autospec=True)
|
||||
def test_save_configuration_swift_noauth(self, mock_put, mock_swift):
|
||||
mock_session = mock.Mock()
|
||||
mock_session.get_token.return_value = None
|
||||
mock_swift.return_value = mock_session
|
||||
cfg.CONF.molds.storage = 'swift'
|
||||
url = 'https://example.com/file1'
|
||||
data = {'key': 'value'}
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertRaises(
|
||||
exception.IronicException,
|
||||
molds.save_configuration,
|
||||
task, url, data)
|
||||
|
||||
@mock.patch.object(requests, 'put', autospec=True)
|
||||
def test_save_configuration_http(self, mock_put):
|
||||
cfg.CONF.molds.storage = 'http'
|
||||
cfg.CONF.molds.user = 'user'
|
||||
cfg.CONF.molds.password = 'password'
|
||||
url = 'https://example.com/file1'
|
||||
data = {'key': 'value'}
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
molds.save_configuration(task, url, data)
|
||||
|
||||
mock_put.assert_called_once_with(
|
||||
url, '{\n "key": "value"\n}',
|
||||
headers={'Authorization': 'Basic dXNlcjpwYXNzd29yZA=='})
|
||||
|
||||
@mock.patch.object(requests, 'put', autospec=True)
|
||||
def test_save_configuration_http_noauth(self, mock_put):
|
||||
cfg.CONF.molds.storage = 'http'
|
||||
cfg.CONF.molds.user = None
|
||||
cfg.CONF.molds.password = None
|
||||
url = 'https://example.com/file1'
|
||||
data = {'key': 'value'}
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
molds.save_configuration(task, url, data)
|
||||
mock_put.assert_called_once_with(
|
||||
url, '{\n "key": "value"\n}',
|
||||
headers=None)
|
||||
|
||||
@mock.patch.object(requests, 'put', autospec=True)
|
||||
def test_save_configuration_http_error(self, mock_put):
|
||||
cfg.CONF.molds.storage = 'http'
|
||||
cfg.CONF.molds.user = 'user'
|
||||
cfg.CONF.molds.password = 'password'
|
||||
response = mock.MagicMock()
|
||||
response.status_code = 404
|
||||
response.raise_for_status.side_effect = requests.exceptions.HTTPError
|
||||
mock_put.return_value = response
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertRaises(
|
||||
requests.exceptions.HTTPError,
|
||||
molds.save_configuration,
|
||||
task,
|
||||
'https://example.com/file2',
|
||||
{'key': 'value'})
|
||||
mock_put.assert_called_once_with(
|
||||
'https://example.com/file2', '{\n "key": "value"\n}',
|
||||
headers={'Authorization': 'Basic dXNlcjpwYXNzd29yZA=='})
|
||||
|
||||
@mock.patch.object(requests, 'put', autospec=True)
|
||||
def test_save_configuration_connection_error(self, mock_put):
|
||||
cfg.CONF.molds.storage = 'http'
|
||||
cfg.CONF.molds.user = 'user'
|
||||
cfg.CONF.molds.password = 'password'
|
||||
cfg.CONF.molds.retry_interval = 0
|
||||
cfg.CONF.molds.retry_attempts = 3
|
||||
response = mock.MagicMock()
|
||||
mock_put.side_effect = [
|
||||
requests.exceptions.ConnectTimeout,
|
||||
requests.exceptions.ConnectionError,
|
||||
response]
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
molds.save_configuration(
|
||||
task, 'https://example.com/file2', {'key': 'value'})
|
||||
mock_put.assert_called_with(
|
||||
'https://example.com/file2', '{\n "key": "value"\n}',
|
||||
headers={'Authorization': 'Basic dXNlcjpwYXNzd29yZA=='})
|
||||
self.assertEqual(mock_put.call_count, 3)
|
||||
|
||||
@mock.patch.object(requests, 'put', autospec=True)
|
||||
def test_save_configuration_connection_error_exceeded(self, mock_put):
|
||||
cfg.CONF.molds.storage = 'http'
|
||||
cfg.CONF.molds.user = 'user'
|
||||
cfg.CONF.molds.password = 'password'
|
||||
cfg.CONF.molds.retry_interval = 0
|
||||
cfg.CONF.molds.retry_attempts = 2
|
||||
mock_put.side_effect = [
|
||||
requests.exceptions.ConnectTimeout,
|
||||
requests.exceptions.ConnectionError]
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertRaises(
|
||||
requests.exceptions.ConnectionError,
|
||||
molds.save_configuration,
|
||||
task,
|
||||
'https://example.com/file2',
|
||||
{'key': 'value'})
|
||||
mock_put.assert_called_with(
|
||||
'https://example.com/file2', '{\n "key": "value"\n}',
|
||||
headers={'Authorization': 'Basic dXNlcjpwYXNzd29yZA=='})
|
||||
self.assertEqual(mock_put.call_count, 2)
|
||||
|
||||
@mock.patch.object(swift, 'get_swift_session', autospec=True)
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test_get_configuration_swift(self, mock_get, mock_swift):
|
||||
mock_session = mock.Mock()
|
||||
mock_session.get_token.return_value = 'token'
|
||||
mock_swift.return_value = mock_session
|
||||
cfg.CONF.molds.storage = 'swift'
|
||||
response = mock.MagicMock()
|
||||
response.status_code = 200
|
||||
response.json.return_value = {'key': 'value'}
|
||||
mock_get.return_value = response
|
||||
url = 'https://example.com/file1'
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
result = molds.get_configuration(task, url)
|
||||
|
||||
mock_get.assert_called_once_with(
|
||||
url, headers={'X-Auth-Token': 'token'})
|
||||
self.assertJsonEqual({'key': 'value'}, result)
|
||||
|
||||
@mock.patch.object(swift, 'get_swift_session', autospec=True)
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test_get_configuration_swift_noauth(self, mock_get, mock_swift):
|
||||
mock_session = mock.Mock()
|
||||
mock_session.get_token.return_value = None
|
||||
mock_swift.return_value = mock_session
|
||||
cfg.CONF.molds.storage = 'swift'
|
||||
url = 'https://example.com/file1'
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertRaises(
|
||||
exception.IronicException,
|
||||
molds.get_configuration,
|
||||
task, url)
|
||||
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test_get_configuration_http(self, mock_get):
|
||||
cfg.CONF.molds.storage = 'http'
|
||||
cfg.CONF.molds.user = 'user'
|
||||
cfg.CONF.molds.password = 'password'
|
||||
response = mock.MagicMock()
|
||||
response.status_code = 200
|
||||
response.json.return_value = {'key': 'value'}
|
||||
mock_get.return_value = response
|
||||
url = 'https://example.com/file2'
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
result = molds.get_configuration(task, url)
|
||||
|
||||
mock_get.assert_called_once_with(
|
||||
url, headers={'Authorization': 'Basic dXNlcjpwYXNzd29yZA=='})
|
||||
self.assertJsonEqual({"key": "value"}, result)
|
||||
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test_get_configuration_http_noauth(self, mock_get):
|
||||
cfg.CONF.molds.storage = 'http'
|
||||
cfg.CONF.molds.user = None
|
||||
cfg.CONF.molds.password = None
|
||||
response = mock.MagicMock()
|
||||
response.status_code = 200
|
||||
response.json.return_value = {'key': 'value'}
|
||||
mock_get.return_value = response
|
||||
url = 'https://example.com/file2'
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
result = molds.get_configuration(task, url)
|
||||
|
||||
mock_get.assert_called_once_with(url, headers=None)
|
||||
self.assertJsonEqual({"key": "value"}, result)
|
||||
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test_get_configuration_http_error(self, mock_get):
|
||||
cfg.CONF.molds.storage = 'http'
|
||||
cfg.CONF.molds.user = 'user'
|
||||
cfg.CONF.molds.password = 'password'
|
||||
response = mock.MagicMock()
|
||||
response.status_code = 404
|
||||
response.raise_for_status.side_effect = requests.exceptions.HTTPError
|
||||
mock_get.return_value = response
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertRaises(
|
||||
requests.exceptions.HTTPError,
|
||||
molds.get_configuration,
|
||||
task,
|
||||
'https://example.com/file2')
|
||||
mock_get.assert_called_once_with(
|
||||
'https://example.com/file2',
|
||||
headers={'Authorization': 'Basic dXNlcjpwYXNzd29yZA=='})
|
||||
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test_get_configuration_connection_error(self, mock_get):
|
||||
cfg.CONF.molds.storage = 'http'
|
||||
cfg.CONF.molds.user = 'user'
|
||||
cfg.CONF.molds.password = 'password'
|
||||
cfg.CONF.molds.retry_interval = 0
|
||||
cfg.CONF.molds.retry_attempts = 3
|
||||
response = mock.MagicMock()
|
||||
mock_get.side_effect = [
|
||||
requests.exceptions.ConnectTimeout,
|
||||
requests.exceptions.ConnectionError,
|
||||
response]
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
molds.get_configuration(
|
||||
task, 'https://example.com/file2')
|
||||
mock_get.assert_called_with(
|
||||
'https://example.com/file2',
|
||||
headers={'Authorization': 'Basic dXNlcjpwYXNzd29yZA=='})
|
||||
self.assertEqual(mock_get.call_count, 3)
|
||||
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test_get_configuration_mold_connection_error_exceeded(self, mock_get):
|
||||
cfg.CONF.molds.storage = 'http'
|
||||
cfg.CONF.molds.user = 'user'
|
||||
cfg.CONF.molds.password = 'password'
|
||||
cfg.CONF.molds.retry_interval = 0
|
||||
cfg.CONF.molds.retry_attempts = 2
|
||||
mock_get.side_effect = [
|
||||
requests.exceptions.ConnectTimeout,
|
||||
requests.exceptions.ConnectionError]
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertRaises(
|
||||
requests.exceptions.ConnectionError,
|
||||
molds.get_configuration,
|
||||
task,
|
||||
'https://example.com/file2')
|
||||
mock_get.assert_called_with(
|
||||
'https://example.com/file2',
|
||||
headers={'Authorization': 'Basic dXNlcjpwYXNzd29yZA=='})
|
||||
self.assertEqual(mock_get.call_count, 2)
|
@ -19,7 +19,6 @@ from unittest import mock
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import timeutils
|
||||
import requests
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common import swift
|
||||
@ -409,270 +408,3 @@ class MixinVendorInterfaceTestCase(db_base.DbTestCase):
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
self.vendor.validate,
|
||||
task, method='fake_method')
|
||||
|
||||
|
||||
class ConfigurationMoldTestCase(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ConfigurationMoldTestCase, self).setUp()
|
||||
self.node = obj_utils.create_test_node(self.context)
|
||||
|
||||
@mock.patch.object(swift, 'get_swift_session', autospec=True)
|
||||
@mock.patch.object(requests, 'put', autospec=True)
|
||||
def test_save_configuration_mold_swift(self, mock_put, mock_swift):
|
||||
mock_session = mock.Mock()
|
||||
mock_session.get_token.return_value = 'token'
|
||||
mock_swift.return_value = mock_session
|
||||
driver_utils.CONF.mold_storage = 'swift'
|
||||
url = 'https://example.com/file1'
|
||||
data = {'key': 'value'}
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
driver_utils.save_configuration_mold(task, url, data)
|
||||
|
||||
mock_put.assert_called_once_with(url, '{\n "key": "value"\n}',
|
||||
headers={'X-Auth-Token': 'token'})
|
||||
|
||||
@mock.patch.object(swift, 'get_swift_session', autospec=True)
|
||||
@mock.patch.object(requests, 'put', autospec=True)
|
||||
def test_save_configuration_mold_swift_noauth(self, mock_put, mock_swift):
|
||||
mock_session = mock.Mock()
|
||||
mock_session.get_token.return_value = None
|
||||
mock_swift.return_value = mock_session
|
||||
driver_utils.CONF.mold_storage = 'swift'
|
||||
url = 'https://example.com/file1'
|
||||
data = {'key': 'value'}
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertRaises(
|
||||
exception.IronicException,
|
||||
driver_utils.save_configuration_mold,
|
||||
task, url, data)
|
||||
|
||||
@mock.patch.object(requests, 'put', autospec=True)
|
||||
def test_save_configuration_mold_http(self, mock_put):
|
||||
driver_utils.CONF.mold_storage = 'http'
|
||||
driver_utils.CONF.mold_user = 'user'
|
||||
driver_utils.CONF.mold_password = 'password'
|
||||
url = 'https://example.com/file1'
|
||||
data = {'key': 'value'}
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
driver_utils.save_configuration_mold(task, url, data)
|
||||
|
||||
mock_put.assert_called_once_with(
|
||||
url, '{\n "key": "value"\n}',
|
||||
headers={'Authorization': 'Basic dXNlcjpwYXNzd29yZA=='})
|
||||
|
||||
@mock.patch.object(requests, 'put', autospec=True)
|
||||
def test_save_configuration_mold_http_noauth(self, mock_put):
|
||||
driver_utils.CONF.mold_storage = 'http'
|
||||
driver_utils.CONF.mold_user = None
|
||||
driver_utils.CONF.mold_password = None
|
||||
url = 'https://example.com/file1'
|
||||
data = {'key': 'value'}
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
driver_utils.save_configuration_mold(task, url, data)
|
||||
|
||||
mock_put.assert_called_once_with(
|
||||
url, '{\n "key": "value"\n}',
|
||||
headers=None)
|
||||
|
||||
@mock.patch.object(requests, 'put', autospec=True)
|
||||
def test_save_configuration_mold_http_error(self, mock_put):
|
||||
driver_utils.CONF.mold_storage = 'http'
|
||||
driver_utils.CONF.mold_user = 'user'
|
||||
driver_utils.CONF.mold_password = 'password'
|
||||
response = mock.MagicMock()
|
||||
response.status_code = 404
|
||||
response.raise_for_status.side_effect = requests.exceptions.HTTPError
|
||||
mock_put.return_value = response
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertRaises(
|
||||
requests.exceptions.HTTPError,
|
||||
driver_utils.save_configuration_mold,
|
||||
task,
|
||||
'https://example.com/file2',
|
||||
{'key': 'value'})
|
||||
mock_put.assert_called_once_with(
|
||||
'https://example.com/file2', '{\n "key": "value"\n}',
|
||||
headers={'Authorization': 'Basic dXNlcjpwYXNzd29yZA=='})
|
||||
|
||||
@mock.patch.object(requests, 'put', autospec=True)
|
||||
def test_save_configuration_mold_connection_error(self, mock_put):
|
||||
driver_utils.CONF.mold_storage = 'http'
|
||||
driver_utils.CONF.mold_user = 'user'
|
||||
driver_utils.CONF.mold_password = 'password'
|
||||
driver_utils.CONF.mold_retry_interval = 0
|
||||
driver_utils.CONF.mold_retry_attempts = 3
|
||||
response = mock.MagicMock()
|
||||
mock_put.side_effect = [
|
||||
requests.exceptions.ConnectTimeout,
|
||||
requests.exceptions.ConnectionError,
|
||||
response]
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
driver_utils.save_configuration_mold(
|
||||
task, 'https://example.com/file2', {'key': 'value'})
|
||||
mock_put.assert_called_with(
|
||||
'https://example.com/file2', '{\n "key": "value"\n}',
|
||||
headers={'Authorization': 'Basic dXNlcjpwYXNzd29yZA=='})
|
||||
self.assertEqual(mock_put.call_count, 3)
|
||||
|
||||
@mock.patch.object(requests, 'put', autospec=True)
|
||||
def test_save_configuration_mold_connection_error_exceeded(self, mock_put):
|
||||
driver_utils.CONF.mold_storage = 'http'
|
||||
driver_utils.CONF.mold_user = 'user'
|
||||
driver_utils.CONF.mold_password = 'password'
|
||||
driver_utils.CONF.mold_retry_interval = 0
|
||||
driver_utils.CONF.mold_retry_attempts = 2
|
||||
mock_put.side_effect = [
|
||||
requests.exceptions.ConnectTimeout,
|
||||
requests.exceptions.ConnectionError]
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertRaises(
|
||||
requests.exceptions.ConnectionError,
|
||||
driver_utils.save_configuration_mold,
|
||||
task,
|
||||
'https://example.com/file2',
|
||||
{'key': 'value'})
|
||||
mock_put.assert_called_with(
|
||||
'https://example.com/file2', '{\n "key": "value"\n}',
|
||||
headers={'Authorization': 'Basic dXNlcjpwYXNzd29yZA=='})
|
||||
self.assertEqual(mock_put.call_count, 2)
|
||||
|
||||
@mock.patch.object(swift, 'get_swift_session', autospec=True)
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test_get_configuration_mold_swift(self, mock_get, mock_swift):
|
||||
mock_session = mock.Mock()
|
||||
mock_session.get_token.return_value = 'token'
|
||||
mock_swift.return_value = mock_session
|
||||
driver_utils.CONF.mold_storage = 'swift'
|
||||
response = mock.MagicMock()
|
||||
response.status_code = 200
|
||||
response.json.return_value = {'key': 'value'}
|
||||
mock_get.return_value = response
|
||||
url = 'https://example.com/file1'
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
result = driver_utils.get_configuration_mold(task, url)
|
||||
|
||||
mock_get.assert_called_once_with(
|
||||
url, headers={'X-Auth-Token': 'token'})
|
||||
self.assertJsonEqual({'key': 'value'}, result)
|
||||
|
||||
@mock.patch.object(swift, 'get_swift_session', autospec=True)
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test_get_configuration_mold_swift_noauth(self, mock_get, mock_swift):
|
||||
mock_session = mock.Mock()
|
||||
mock_session.get_token.return_value = None
|
||||
mock_swift.return_value = mock_session
|
||||
driver_utils.CONF.mold_storage = 'swift'
|
||||
url = 'https://example.com/file1'
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertRaises(
|
||||
exception.IronicException,
|
||||
driver_utils.get_configuration_mold,
|
||||
task, url)
|
||||
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test_get_configuration_mold_http(self, mock_get):
|
||||
driver_utils.CONF.mold_storage = 'http'
|
||||
driver_utils.CONF.mold_user = 'user'
|
||||
driver_utils.CONF.mold_password = 'password'
|
||||
response = mock.MagicMock()
|
||||
response.status_code = 200
|
||||
response.json.return_value = {'key': 'value'}
|
||||
mock_get.return_value = response
|
||||
url = 'https://example.com/file2'
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
result = driver_utils.get_configuration_mold(task, url)
|
||||
|
||||
mock_get.assert_called_once_with(
|
||||
url, headers={'Authorization': 'Basic dXNlcjpwYXNzd29yZA=='})
|
||||
self.assertJsonEqual({"key": "value"}, result)
|
||||
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test_get_configuration_mold_http_noauth(self, mock_get):
|
||||
driver_utils.CONF.mold_storage = 'http'
|
||||
driver_utils.CONF.mold_user = None
|
||||
driver_utils.CONF.mold_password = None
|
||||
response = mock.MagicMock()
|
||||
response.status_code = 200
|
||||
response.json.return_value = {'key': 'value'}
|
||||
mock_get.return_value = response
|
||||
url = 'https://example.com/file2'
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
result = driver_utils.get_configuration_mold(task, url)
|
||||
|
||||
mock_get.assert_called_once_with(url, headers=None)
|
||||
self.assertJsonEqual({"key": "value"}, result)
|
||||
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test_get_configuration_mold_http_error(self, mock_get):
|
||||
driver_utils.CONF.mold_storage = 'http'
|
||||
driver_utils.CONF.mold_user = 'user'
|
||||
driver_utils.CONF.mold_password = 'password'
|
||||
response = mock.MagicMock()
|
||||
response.status_code = 404
|
||||
response.raise_for_status.side_effect = requests.exceptions.HTTPError
|
||||
mock_get.return_value = response
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertRaises(
|
||||
requests.exceptions.HTTPError,
|
||||
driver_utils.get_configuration_mold,
|
||||
task,
|
||||
'https://example.com/file2')
|
||||
mock_get.assert_called_once_with(
|
||||
'https://example.com/file2',
|
||||
headers={'Authorization': 'Basic dXNlcjpwYXNzd29yZA=='})
|
||||
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test_get_configuration_mold_connection_error(self, mock_get):
|
||||
driver_utils.CONF.mold_storage = 'http'
|
||||
driver_utils.CONF.mold_user = 'user'
|
||||
driver_utils.CONF.mold_password = 'password'
|
||||
driver_utils.CONF.mold_retry_interval = 0
|
||||
driver_utils.CONF.mold_retry_attempts = 3
|
||||
response = mock.MagicMock()
|
||||
mock_get.side_effect = [
|
||||
requests.exceptions.ConnectTimeout,
|
||||
requests.exceptions.ConnectionError,
|
||||
response]
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
driver_utils.get_configuration_mold(
|
||||
task, 'https://example.com/file2')
|
||||
mock_get.assert_called_with(
|
||||
'https://example.com/file2',
|
||||
headers={'Authorization': 'Basic dXNlcjpwYXNzd29yZA=='})
|
||||
self.assertEqual(mock_get.call_count, 3)
|
||||
|
||||
@mock.patch.object(requests, 'get', autospec=True)
|
||||
def test_get_configuration_mold_connection_error_exceeded(self, mock_get):
|
||||
driver_utils.CONF.mold_storage = 'http'
|
||||
driver_utils.CONF.mold_user = 'user'
|
||||
driver_utils.CONF.mold_password = 'password'
|
||||
driver_utils.CONF.mold_retry_interval = 0
|
||||
driver_utils.CONF.mold_retry_attempts = 2
|
||||
mock_get.side_effect = [
|
||||
requests.exceptions.ConnectTimeout,
|
||||
requests.exceptions.ConnectionError]
|
||||
|
||||
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||
self.assertRaises(
|
||||
requests.exceptions.ConnectionError,
|
||||
driver_utils.get_configuration_mold,
|
||||
task,
|
||||
'https://example.com/file2')
|
||||
mock_get.assert_called_with(
|
||||
'https://example.com/file2',
|
||||
headers={'Authorization': 'Basic dXNlcjpwYXNzd29yZA=='})
|
||||
self.assertEqual(mock_get.call_count, 2)
|
||||
|
Loading…
Reference in New Issue
Block a user