Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-sushy for openSUSE:Factory checked in at 2023-01-05 15:00:35 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-sushy (Old) and /work/SRC/openSUSE:Factory/.python-sushy.new.1563 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-sushy" Thu Jan 5 15:00:35 2023 rev:11 rq:1055985 version:4.4.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-sushy/python-sushy.changes 2022-10-03 13:47:18.845686280 +0200 +++ /work/SRC/openSUSE:Factory/.python-sushy.new.1563/python-sushy.changes 2023-01-05 15:00:54.669038709 +0100 @@ -1,0 +2,13 @@ +Mon Jan 2 09:11:53 UTC 2023 - cloud-de...@suse.de + +- update to version 4.4.0 + - Fix misuse of assertTrue + - Update master for stable/zed + - Improve resiliency of eTag handling + - Increase server side retries + - Fix misuse of assertIsNone + - Update release versions for yoga and zed + - Add Python3 antelope unit tests + - Make server connection retries configurable + +------------------------------------------------------------------- Old: ---- sushy-4.3.0.tar.gz New: ---- sushy-4.4.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-sushy.spec ++++++ --- /var/tmp/diff_new_pack.Rthm5i/_old 2023-01-05 15:00:55.245041746 +0100 +++ /var/tmp/diff_new_pack.Rthm5i/_new 2023-01-05 15:00:55.253041788 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-sushy # -# Copyright (c) 2022 SUSE LLC +# Copyright (c) 2023 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,13 +17,13 @@ Name: python-sushy -Version: 4.3.0 +Version: 4.4.0 Release: 0 Summary: Python library to communicate with Redfish based systems License: Apache-2.0 Group: Development/Languages/Python URL: https://docs.openstack.org/sushy -Source0: https://files.pythonhosted.org/packages/source/s/sushy/sushy-4.3.0.tar.gz +Source0: https://files.pythonhosted.org/packages/source/s/sushy/sushy-4.4.0.tar.gz BuildRequires: openstack-macros BuildRequires: python3-oslotest BuildRequires: python3-pbr >= 2.0.0 @@ -62,7 +62,7 @@ This package contains the documentation. %prep -%autosetup -p1 -n sushy-4.3.0 +%autosetup -p1 -n sushy-4.4.0 %py_req_cleanup %build ++++++ sushy-4.3.0.tar.gz -> sushy-4.4.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/AUTHORS new/sushy-4.4.0/AUTHORS --- old/sushy-4.3.0/AUTHORS 2022-08-26 19:56:31.000000000 +0200 +++ new/sushy-4.4.0/AUTHORS 2022-11-22 16:26:19.000000000 +0100 @@ -45,6 +45,7 @@ Sean McGinnis <sean.mcgin...@gmail.com> Shivanand Tendulker <stendul...@gmail.com> Steve Baker <sba...@redhat.com> +Takashi Natsume <takanat...@gmail.com> Thomas Goirand <z...@debian.org> Varsha <varsha.verma.ee...@itbhu.ac.in> Vu Cong Tuan <tua...@vn.fujitsu.com> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/ChangeLog new/sushy-4.4.0/ChangeLog --- old/sushy-4.3.0/ChangeLog 2022-08-26 19:56:31.000000000 +0200 +++ new/sushy-4.4.0/ChangeLog 2022-11-22 16:26:18.000000000 +0100 @@ -1,6 +1,18 @@ CHANGES ======= +4.4.0 +----- + +* Make server connection retries configurable +* Increase server side retries +* Improve resiliency of eTag handling +* Update release versions for yoga and zed +* Fix misuse of assertTrue +* Add Python3 antelope unit tests +* Update master for stable/zed +* Fix misuse of assertIsNone + 4.3.0 ----- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/PKG-INFO new/sushy-4.4.0/PKG-INFO --- old/sushy-4.3.0/PKG-INFO 2022-08-26 19:56:32.073955500 +0200 +++ new/sushy-4.4.0/PKG-INFO 2022-11-22 16:26:19.412175000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: sushy -Version: 4.3.0 +Version: 4.4.0 Summary: Sushy is a small Python library to communicate with Redfish based systems Home-page: https://docs.openstack.org/sushy/latest/ Author: OpenStack diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/releasenotes/notes/config-server-side-retries-d16824019bd709a2.yaml new/sushy-4.4.0/releasenotes/notes/config-server-side-retries-d16824019bd709a2.yaml --- old/sushy-4.3.0/releasenotes/notes/config-server-side-retries-d16824019bd709a2.yaml 1970-01-01 01:00:00.000000000 +0100 +++ new/sushy-4.4.0/releasenotes/notes/config-server-side-retries-d16824019bd709a2.yaml 2022-11-22 16:25:52.000000000 +0100 @@ -0,0 +1,9 @@ +--- +features: + - | + Adds two new configuration options: + ``server_side_retries`` allows to set a custom value for the number of + times we should retry a GET request in case of a server error, + defaults to 10; + ``server_side_retries_delay`` allows to customize the time in seconds + between the request retries, defaults to 3. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/releasenotes/notes/increase-server-retries-5f11edde8ee0b461.yaml new/sushy-4.4.0/releasenotes/notes/increase-server-retries-5f11edde8ee0b461.yaml --- old/sushy-4.3.0/releasenotes/notes/increase-server-retries-5f11edde8ee0b461.yaml 1970-01-01 01:00:00.000000000 +0100 +++ new/sushy-4.4.0/releasenotes/notes/increase-server-retries-5f11edde8ee0b461.yaml 2022-11-22 16:25:52.000000000 +0100 @@ -0,0 +1,6 @@ +fixes: + - | + To avoid timeouts with recent versions of firmwares we need to increase + the number of server side retries. + For example, in idrac firmware series 5.x the time gap between operations + is almost 20 seconds instead of the 10 seconds in the 4.x series. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/releasenotes/notes/releasenote-d7138d1e1d414632.yaml new/sushy-4.4.0/releasenotes/notes/releasenote-d7138d1e1d414632.yaml --- old/sushy-4.3.0/releasenotes/notes/releasenote-d7138d1e1d414632.yaml 1970-01-01 01:00:00.000000000 +0100 +++ new/sushy-4.4.0/releasenotes/notes/releasenote-d7138d1e1d414632.yaml 2022-11-22 16:25:52.000000000 +0100 @@ -0,0 +1,10 @@ +--- +fixes: + - | + Alters eTag handling in PATCH requests: First, the original eTag is used. + In case of a failure, if the eTag provided was weak, it is converted to + a strong format by removing the weak prefix. If this approach is not + applicable or fails, the final attempt is made omitting the eTag entirely. + By taking this approach, no workarounds are applied if BMC is handling + eTags as expected and in case of failures, known workarounds are + attempted, improving overall resiliency. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/releasenotes/source/index.rst new/sushy-4.4.0/releasenotes/source/index.rst --- old/sushy-4.3.0/releasenotes/source/index.rst 2022-08-26 19:55:50.000000000 +0200 +++ new/sushy-4.4.0/releasenotes/source/index.rst 2022-11-22 16:25:52.000000000 +0100 @@ -6,6 +6,7 @@ :maxdepth: 1 unreleased + zed yoga xena wallaby diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/releasenotes/source/yoga.rst new/sushy-4.4.0/releasenotes/source/yoga.rst --- old/sushy-4.3.0/releasenotes/source/yoga.rst 2022-08-26 19:55:50.000000000 +0200 +++ new/sushy-4.4.0/releasenotes/source/yoga.rst 2022-11-22 16:25:52.000000000 +0100 @@ -1,6 +1,6 @@ -========================= -Yoga Series Release Notes -========================= +========================================= +Yoga Series (4.0.0 - 4.1.x) Release Notes +========================================= .. release-notes:: :branch: stable/yoga diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/releasenotes/source/zed.rst new/sushy-4.4.0/releasenotes/source/zed.rst --- old/sushy-4.3.0/releasenotes/source/zed.rst 1970-01-01 01:00:00.000000000 +0100 +++ new/sushy-4.4.0/releasenotes/source/zed.rst 2022-11-22 16:25:52.000000000 +0100 @@ -0,0 +1,6 @@ +======================================== +Zed Series (4.2.0 - 4.3.x) Release Notes +======================================== + +.. release-notes:: + :branch: stable/zed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/sushy/connector.py new/sushy-4.4.0/sushy/connector.py --- old/sushy-4.3.0/sushy/connector.py 2022-08-26 19:55:50.000000000 +0200 +++ new/sushy-4.4.0/sushy/connector.py 2022-11-22 16:25:52.000000000 +0100 @@ -13,7 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. +from http import client as http_client import logging +import re import time from urllib import parse as urlparse @@ -26,21 +28,20 @@ LOG = logging.getLogger(__name__) -_SERVER_SIDE_RETRIES = 5 -_SERVER_SIDE_RETRY_DELAY = 3 - - class Connector(object): def __init__( self, url, username=None, password=None, verify=True, - response_callback=None): + response_callback=None, server_side_retries=0, + server_side_retries_delay=0): self._url = url self._verify = verify self._session = requests.Session() self._session.verify = self._verify self._response_callback = response_callback self._auth = None + self._server_side_retries = server_side_retries + self._server_side_retries_delay = server_side_retries_delay # NOTE(TheJulia): In order to help prevent recursive post operations # by allowing us to understand that we should stop authentication. @@ -85,8 +86,7 @@ self._session.close() def _op(self, method, path='', data=None, headers=None, blocking=False, - timeout=60, server_side_retries=_SERVER_SIDE_RETRIES, - **extra_session_req_kwargs): + timeout=60, **extra_session_req_kwargs): """Generic RESTful request handler. :param method: The HTTP method to be used, e.g: GET, POST, @@ -197,16 +197,16 @@ "%s", e.message) raise except exceptions.ServerSideError as e: - if method.lower() != 'get' or server_side_retries <= 0: + if method.lower() != 'get' or self._server_side_retries <= 0: raise else: LOG.warning('Got server side error %s in response to a ' 'GET request, retrying after %d seconds', - e, _SERVER_SIDE_RETRY_DELAY) - time.sleep(_SERVER_SIDE_RETRY_DELAY) + e, self._server_side_retries) + time.sleep(self._server_side_retries_delay) + self._server_side_retries -= 1 return self._op(method, path, data=data, headers=headers, blocking=blocking, timeout=timeout, - server_side_retries=server_side_retries - 1, **extra_session_req_kwargs) if blocking and response.status_code == 202: @@ -268,13 +268,93 @@ blocking=blocking, timeout=timeout, **extra_session_req_kwargs) - def patch(self, path='', data=None, headers=None, blocking=False, - timeout=60, **extra_session_req_kwargs): + def _etag_handler(self, path='', data=None, headers=None, etag=None, + blocking=False, timeout=60, **extra_session_req_kwargs): + """eTag handler containing workarounds for PATCH requests with eTags. + + :param path: Optional sub-URI path to the resource. + :param data: Optional JSON data. + :param headers: Optional dictionary of headers. + :param etag: eTag string. + :param blocking: Whether to block for asynchronous operations. + :param timeout: Max time in seconds to wait for blocking async call. + :param extra_session_req_kwargs: Optional keyword argument to pass + requests library arguments which would pass on to requests session + object. + :returns: The response object from the requests library. + :raises: ConnectionError + :raises: HTTPError + """ + if etag is not None: + if not headers: + headers = {} + headers['If-Match'] = etag + try: + response = self._op('PATCH', path, data=data, + headers=headers, + blocking=blocking, timeout=timeout, + **extra_session_req_kwargs) + except exceptions.HTTPError as resp: + LOG.warning("Initial request with eTag failed: %s", + resp) + if resp.status_code == http_client.PRECONDITION_FAILED: + # NOTE(janders) if there was no eTag provided AND the response + # is HTTP 412 Precondition Failed, raise the exception + if not etag: + raise + # checking for weak eTag + pattern = re.compile(r'^(W\/)("\w*")$') + match = pattern.match(etag) + if match: + LOG.info("Weak eTag provided with original request to " + "%s. Attempting to conversion to strong eTag " + "and re-trying.", path) + # trying weak eTag converted to strong + headers['If-Match'] = match.group(2) + try: + response = self._op('PATCH', path, data=data, + headers=headers, + blocking=blocking, + timeout=timeout, + **extra_session_req_kwargs) + except exceptions.HTTPError as resp: + if (resp.status_code == http_client. + PRECONDITION_FAILED): + LOG.warning("Request to %s with weak eTag " + "converted to strong eTag also " + "failed. Making the final attempt " + "with no eTag specified.", path) + response = None + if response: + return response + else: + # eTag is strong, if it failed the only other thing + # to try is removing it entirely + # info + LOG.warning("Strong eTag provided - retrying request to " + "%s with eTag removed.", path) + del headers['If-Match'] + try: + response = self._op('PATCH', path, data=data, + headers=headers, + blocking=blocking, timeout=timeout, + **extra_session_req_kwargs) + except exceptions.HTTPError as resp: + LOG.error("Final re-try with eTag removed has failed, " + "raising exception %s", resp) + raise + else: + raise + return response + + def patch(self, path='', data=None, headers=None, etag=None, + blocking=False, timeout=60, **extra_session_req_kwargs): """HTTP PATCH method. :param path: Optional sub-URI path to the resource. :param data: Optional JSON data. :param headers: Optional dictionary of headers. + :param etag: Optional eTag string. :param blocking: Whether to block for asynchronous operations. :param timeout: Max time in seconds to wait for blocking async call. :param extra_session_req_kwargs: Optional keyword argument to pass @@ -284,9 +364,10 @@ :raises: ConnectionError :raises: HTTPError """ - return self._op('PATCH', path, data=data, headers=headers, - blocking=blocking, timeout=timeout, - **extra_session_req_kwargs) + return self._etag_handler(path, data, + headers, etag, + blocking, timeout, + **extra_session_req_kwargs) def put(self, path='', data=None, headers=None, blocking=False, timeout=60, **extra_session_req_kwargs): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/sushy/main.py new/sushy-4.4.0/sushy/main.py --- old/sushy-4.3.0/sushy/main.py 2022-08-26 19:55:50.000000000 +0200 +++ new/sushy-4.4.0/sushy/main.py 2022-11-22 16:25:52.000000000 +0100 @@ -158,7 +158,8 @@ root_prefix='/redfish/v1/', verify=True, auth=None, connector=None, public_connector=None, - language='en'): + language='en', server_side_retries=10, + server_side_retries_delay=3): """A class representing a RootService :param base_url: The base URL to the Redfish controller. It @@ -181,6 +182,10 @@ on the Internet, e.g., for Message Registries. Defaults to None. :param language: RFC 5646 language code for Message Registries. Defaults to 'en'. + :param server_side_retries: Number of times to retry GET requests in + case of server side errors. Defaults to 10. + :param server_side_retries_delay: Time in seconds between retries of + GET requests in case of server side errors. Defaults to 3. """ self._root_prefix = root_prefix if (auth is not None and (password is not None @@ -194,7 +199,10 @@ self._auth = auth super(Sushy, self).__init__( - connector or sushy_connector.Connector(base_url, verify=verify), + connector or sushy_connector.Connector( + base_url, verify=verify, + server_side_retries=server_side_retries, + server_side_retries_delay=server_side_retries_delay), path=self._root_prefix) self._public_connector = public_connector or requests self._language = language diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/sushy/resources/base.py new/sushy-4.4.0/sushy/resources/base.py --- old/sushy-4.3.0/sushy/resources/base.py 2022-08-26 19:55:50.000000000 +0200 +++ new/sushy-4.4.0/sushy/resources/base.py 2022-11-22 16:25:52.000000000 +0100 @@ -20,7 +20,6 @@ import io import json import logging -import re import zipfile import pkg_resources @@ -638,21 +637,7 @@ :returns ETag or None """ - etag = self._get_headers().get('ETag') - if not etag: - return None - - # Note (arne_wiebalck): in case there is a weak Etag, - # return it with and without the qualifier to handle - # vendor implementations which may require one or the - # other (but should actually not use weak tags in the - # first place). - pattern = re.compile(r'^(W\/)("\w*")$') - match = pattern.match(etag) - if match: - return etag + ',' + match.group(2) - - return etag + return self._get_headers().get('ETag') def _get_headers(self): """Returns the HTTP headers of the request for the resource. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/sushy/resources/system/system.py new/sushy-4.4.0/sushy/resources/system/system.py --- old/sushy-4.3.0/sushy/resources/system/system.py 2022-08-26 19:55:50.000000000 +0200 +++ new/sushy-4.4.0/sushy/resources/system/system.py 2022-11-22 16:25:52.000000000 +0100 @@ -276,7 +276,6 @@ data['Boot']['BootSourceOverrideMode'] = fishy_mode - headers = None etag = self._get_etag() path = self.path @@ -290,9 +289,7 @@ # TODO(lucasagomes): Check the return code and response body ? # Probably we should call refresh() as well. - if etag is not None: - headers = {'If-Match': etag} - self._conn.patch(path, data=data, headers=headers) + self._conn.patch(path, data=data, etag=etag) # TODO(etingof): we should remove this method, eventually def set_system_boot_source( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/sushy/tests/unit/resources/compositionservice/test_compositionservice.py new/sushy-4.4.0/sushy/tests/unit/resources/compositionservice/test_compositionservice.py --- old/sushy-4.3.0/sushy/tests/unit/resources/compositionservice/test_compositionservice.py 2022-08-26 19:55:50.000000000 +0200 +++ new/sushy-4.4.0/sushy/tests/unit/resources/compositionservice/test_compositionservice.py 2022-11-22 16:25:52.000000000 +0100 @@ -41,7 +41,7 @@ self.comp_ser._parse_attributes(self.json_doc) self.assertFalse(self.comp_ser.allow_overprovisioning) self.assertTrue(self.comp_ser.allow_zone_affinity) - self.assertTrue(self.comp_ser.description, 'CompositionService1') + self.assertEqual('CompositionService1', self.comp_ser.description) self.assertEqual( 'CompositionService', self.comp_ser.identity) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/sushy/tests/unit/resources/manager/test_virtual_media.py new/sushy-4.4.0/sushy/tests/unit/resources/manager/test_virtual_media.py --- old/sushy-4.3.0/sushy/tests/unit/resources/manager/test_virtual_media.py 2022-08-26 19:55:50.000000000 +0200 +++ new/sushy-4.4.0/sushy/tests/unit/resources/manager/test_virtual_media.py 2022-11-22 16:25:52.000000000 +0100 @@ -175,7 +175,7 @@ ("/redfish/v1/Managers/BMC/VirtualMedia/Floppy1"), data={"Image": "https://www.dmtf.org/freeImages/Sardine.img", "Inserted": True, "WriteProtected": False}, - headers={"If-Match": 'W/"3d7b8a7360bf2941d","3d7b8a7360bf2941d"'}) + headers={"If-Match": 'W/"3d7b8a7360bf2941d"'}) self.assertTrue(self.sys_virtual_media._is_stale) @mock.patch.object(requests, 'post', autospec=True) @@ -239,7 +239,7 @@ self.sys_virtual_media._conn.patch.assert_called_once_with( ("/redfish/v1/Managers/BMC/VirtualMedia/Floppy1"), data={"Image": None, "Inserted": False}, - headers={"If-Match": 'W/"3d7b8a7360bf2941d","3d7b8a7360bf2941d"'}) + headers={"If-Match": 'W/"3d7b8a7360bf2941d"'}) self.assertTrue(self.sys_virtual_media._is_stale) def test_eject_media_pass_empty_dict_415(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/sushy/tests/unit/resources/system/test_system.py new/sushy-4.4.0/sushy/tests/unit/resources/system/test_system.py --- old/sushy-4.3.0/sushy/tests/unit/resources/system/test_system.py 2022-08-26 19:55:50.000000000 +0200 +++ new/sushy-4.4.0/sushy/tests/unit/resources/system/test_system.py 2022-11-22 16:25:52.000000000 +0100 @@ -283,7 +283,7 @@ data={'Boot': {'BootSourceOverrideEnabled': 'Continuous', 'BootSourceOverrideTarget': 'Pxe', 'BootSourceOverrideMode': 'UEFI'}}, - headers=None) + etag=None) def test_set_system_boot_options_no_mode_specified(self): self.sys_inst.set_system_boot_options( @@ -293,7 +293,7 @@ '/redfish/v1/Systems/437XR1138R2', data={'Boot': {'BootSourceOverrideEnabled': 'Once', 'BootSourceOverrideTarget': 'Hdd'}}, - headers=None) + etag=None) def test_set_system_boot_options_no_target_specified(self): self.sys_inst.set_system_boot_options( @@ -303,7 +303,7 @@ '/redfish/v1/Systems/437XR1138R2', data={'Boot': {'BootSourceOverrideEnabled': 'Continuous', 'BootSourceOverrideMode': 'UEFI'}}, - headers=None) + etag=None) def test_set_system_boot_options_no_freq_specified(self): self.sys_inst.set_system_boot_options( @@ -313,13 +313,13 @@ '/redfish/v1/Systems/437XR1138R2', data={'Boot': {'BootSourceOverrideTarget': 'Pxe', 'BootSourceOverrideMode': 'UEFI'}}, - headers=None) + etag=None) def test_set_system_boot_options_nothing_specified(self): self.sys_inst.set_system_boot_options() self.sys_inst._conn.patch.assert_called_once_with( '/redfish/v1/Systems/437XR1138R2', data={}, - headers=None) + etag=None) def test_set_system_boot_options_invalid_target(self): self.assertRaises(exceptions.InvalidParameterValueError, @@ -350,7 +350,7 @@ '/redfish/v1/Systems/437XR1138R2', data={'Boot': {'BootSourceOverrideEnabled': 'Once', 'BootSourceOverrideTarget': 'UsbCd'}}, - headers=None) + etag=None) def test_set_system_boot_options_supermicro_no_usb_cd_boot(self): @@ -363,7 +363,7 @@ '/redfish/v1/Systems/437XR1138R2', data={'Boot': {'BootSourceOverrideEnabled': 'Once', 'BootSourceOverrideTarget': 'Cd'}}, - headers=None) + etag=None) def test_set_system_boot_options_settings_resource_nokia(self): self.conn.get.return_value.headers = {'ETag': '"3d7b838291941d"'} @@ -381,7 +381,7 @@ '/redfish/v1/Systems/Self/SD', data={'Boot': {'BootSourceOverrideEnabled': 'Once', 'BootSourceOverrideTarget': 'Cd'}}, - headers={'If-Match': '"3d7b838291941d"'}) + etag='"3d7b838291941d"') def test_set_system_boot_options_settings_resource(self): self.conn.get.return_value.headers = {'ETag': '"3d7b838291941d"'} @@ -399,7 +399,7 @@ '/redfish/v1/Systems/437XR1138R2/BIOS/Settings', data={'Boot': {'BootSourceOverrideEnabled': 'Once', 'BootSourceOverrideTarget': 'Cd'}}, - headers={'If-Match': '"3d7b838291941d"'}) + etag='"3d7b838291941d"') def test_set_system_boot_source(self): self.sys_inst.set_system_boot_source( @@ -411,7 +411,7 @@ data={'Boot': {'BootSourceOverrideEnabled': 'Continuous', 'BootSourceOverrideTarget': 'Pxe', 'BootSourceOverrideMode': 'UEFI'}}, - headers=None) + etag=None) def test_set_system_boot_source_with_etag(self): self.conn.get.return_value.headers = {'ETag': '"3d7b838291941d"'} @@ -424,7 +424,7 @@ data={'Boot': {'BootSourceOverrideEnabled': 'Continuous', 'BootSourceOverrideTarget': 'Pxe', 'BootSourceOverrideMode': 'UEFI'}}, - headers={'If-Match': '"3d7b838291941d"'}) + etag='"3d7b838291941d"') def test_set_system_boot_source_no_mode_specified(self): self.sys_inst.set_system_boot_source( @@ -434,7 +434,7 @@ '/redfish/v1/Systems/437XR1138R2', data={'Boot': {'BootSourceOverrideEnabled': 'Once', 'BootSourceOverrideTarget': 'Hdd'}}, - headers=None) + etag=None) def test_set_system_boot_source_invalid_target(self): self.assertRaises(exceptions.InvalidParameterValueError, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/sushy/tests/unit/test_connector.py new/sushy-4.4.0/sushy/tests/unit/test_connector.py --- old/sushy-4.3.0/sushy/tests/unit/test_connector.py 2022-08-26 19:55:50.000000000 +0200 +++ new/sushy-4.4.0/sushy/tests/unit/test_connector.py 2022-11-22 16:25:52.000000000 +0100 @@ -164,7 +164,8 @@ self.auth = mock_auth super(ConnectorOpTestCase, self).setUp() self.conn = connector.Connector( - 'http://foo.bar:1234', verify=True) + 'http://foo.bar:1234', verify=True, + server_side_retries=10, server_side_retries_delay=3) self.conn._auth = mock_auth self.data = {'fake': 'data'} self.headers = {'X-Fake': 'header'} @@ -401,8 +402,8 @@ self.conn._op('GET', 'http://foo.bar') exc = cm.exception self.assertEqual(http_client.INTERNAL_SERVER_ERROR, exc.status_code) - self.assertEqual(5, mock_sleep.call_count) - self.assertEqual(6, self.request.call_count) + self.assertEqual(10, mock_sleep.call_count) + self.assertEqual(11, self.request.call_count) def test_access_error(self): self.conn._auth = None @@ -577,3 +578,175 @@ 'POST', 'http://foo.bar', blocking=True) + + @mock.patch.object(connector.Connector, '_op', autospec=True) + def test_etag_handler_strong_etag_ok(self, mock__op): + self.headers['If-Match'] = '"3d7b8a7360bf2941d"' + response = mock.MagicMock(spec=requests.Response) + response.status_code = http_client.OK + data = {'Boot': {'BootSourceOverrideTarget': 'Cd', + 'BootSourceOverrideEnabled': 'Once'}} + mock__op.return_value = response + self.conn._etag_handler(path='/redfish/v1/Systems/1', + headers=self.headers, data=data, + etag=self.headers['If-Match'], + blocking=False, timeout=60) + mock__op.assert_called_once_with( + self.conn, + "PATCH", + '/redfish/v1/Systems/1', + data={'Boot': {'BootSourceOverrideTarget': 'Cd', + 'BootSourceOverrideEnabled': 'Once'}}, + headers={'X-Fake': 'header', + 'If-Match': '"3d7b8a7360bf2941d"'}, + blocking=False, + timeout=60) + + @mock.patch.object(connector.Connector, '_op', autospec=True) + def test_etag_handler_weak_etag_ok(self, mock__op): + self.headers['If-Match'] = 'W/"3d7b8a7360bf2941d"' + response = mock.MagicMock(spec=requests.Response) + response.status_code = http_client.OK + data = {'Boot': {'BootSourceOverrideTarget': 'Cd', + 'BootSourceOverrideEnabled': 'Once'}} + mock__op.return_value = response + self.conn._etag_handler(path='/redfish/v1/Systems/1', + headers=self.headers, data=data, + etag=self.headers['If-Match'], + blocking=False, timeout=60) + mock__op.assert_called_once_with( + self.conn, + "PATCH", + '/redfish/v1/Systems/1', + data={'Boot': {'BootSourceOverrideTarget': 'Cd', + 'BootSourceOverrideEnabled': 'Once'}}, + headers={'X-Fake': 'header', + 'If-Match': 'W/"3d7b8a7360bf2941d"'}, + blocking=False, + timeout=60) + + @mock.patch.object(connector.Connector, '_op', autospec=True) + def test_etag_handler_no_etag_ok(self, mock__op): + response = mock.MagicMock(spec=requests.Response) + response.status_code = http_client.OK + data = {'Boot': {'BootSourceOverrideTarget': 'Cd', + 'BootSourceOverrideEnabled': 'Once'}} + mock__op.return_value = response + self.conn._etag_handler(path='/redfish/v1/Systems/1', + headers=self.headers, data=data, + blocking=False, timeout=60) + mock__op.assert_called_once_with( + self.conn, + "PATCH", + '/redfish/v1/Systems/1', + data={'Boot': {'BootSourceOverrideTarget': 'Cd', + 'BootSourceOverrideEnabled': 'Once'}}, + headers={'X-Fake': 'header'}, + blocking=False, + timeout=60) + + @mock.patch.object(connector.Connector, '_op', autospec=True) + def test_etag_handler_weak_etag_fallback_to_strong(self, mock__op): + self.headers['If-Match'] = 'W/"3d7b8a7360bf2941d"' + target_uri = '/redfish/v1/Systems/1' + response_412 = mock.MagicMock(spec=requests.Response) + response_412.status_code = http_client.PRECONDITION_FAILED + response_200 = mock.MagicMock(spec=requests.Response) + response_200.status_code = http_client.OK + mock__op.side_effect = [ + exceptions.HTTPError(method='PATCH', url=target_uri, + response=response_412), + response_200] + + data = {'Boot': {'BootSourceOverrideTarget': 'Cd', + 'BootSourceOverrideEnabled': 'Once'}} + self.conn._etag_handler(path=target_uri, headers=self.headers, + data=data, etag=self.headers['If-Match'], + blocking=False, timeout=60) + mock__op.assert_called_with( + self.conn, + "PATCH", + '/redfish/v1/Systems/1', + data={'Boot': {'BootSourceOverrideTarget': 'Cd', + 'BootSourceOverrideEnabled': 'Once'}}, + headers={'X-Fake': 'header', + 'If-Match': '"3d7b8a7360bf2941d"'}, + blocking=False, + timeout=60) + + @mock.patch.object(connector.Connector, '_op', autospec=True) + def test_etag_handler_weak_etag_fallback_to_no_etag(self, mock__op): + self.headers['If-Match'] = 'W/"3d7b8a7360bf2941d"' + target_uri = '/redfish/v1/Systems/1' + response_412 = mock.MagicMock(spec=requests.Response) + response_412.status_code = http_client.PRECONDITION_FAILED + response_200 = mock.MagicMock(spec=requests.Response) + response_200.status_code = http_client.OK + mock__op.side_effect = [ + exceptions.HTTPError(method='PATCH', url=target_uri, + response=response_412), + exceptions.HTTPError(method='PATCH', url=target_uri, + response=response_412), + response_200] + + data = {'Boot': {'BootSourceOverrideTarget': 'Cd', + 'BootSourceOverrideEnabled': 'Once'}} + self.conn._etag_handler(path=target_uri, headers=self.headers, + data=data, etag=self.headers['If-Match'], + blocking=False, timeout=60) + mock__op.assert_called_with( + self.conn, + "PATCH", + '/redfish/v1/Systems/1', + data={'Boot': {'BootSourceOverrideTarget': 'Cd', + 'BootSourceOverrideEnabled': 'Once'}}, + headers={'X-Fake': 'header'}, + blocking=False, + timeout=60) + + @mock.patch.object(connector.Connector, '_op', autospec=True) + def test_etag_handler_strong_etag_fallback_to_no_etag(self, mock__op): + self.headers['If-Match'] = '"3d7b8a7360bf2941d"' + target_uri = '/redfish/v1/Systems/1' + response_412 = mock.MagicMock(spec=requests.Response) + response_412.status_code = http_client.PRECONDITION_FAILED + response_200 = mock.MagicMock(spec=requests.Response) + response_200.status_code = http_client.OK + mock__op.side_effect = [ + exceptions.HTTPError(method='PATCH', url=target_uri, + response=response_412), + response_200] + + data = {'Boot': {'BootSourceOverrideTarget': 'Cd', + 'BootSourceOverrideEnabled': 'Once'}} + self.conn._etag_handler(path=target_uri, headers=self.headers, + data=data, etag=self.headers['If-Match'], + blocking=False, timeout=60) + mock__op.assert_called_with( + self.conn, + "PATCH", + '/redfish/v1/Systems/1', + data={'Boot': {'BootSourceOverrideTarget': 'Cd', + 'BootSourceOverrideEnabled': 'Once'}}, + headers={'X-Fake': 'header'}, + blocking=False, + timeout=60) + + @mock.patch.object(connector.Connector, '_op', autospec=True) + def test_etag_handler_fail_other_exception(self, mock__op): + self.headers['If-Match'] = 'W/"3d7b8a7360bf2941d"' + target_uri = '/redfish/v1/Systems/1' + data = {'Boot': {'BootSourceOverrideTarget': 'Cd', + 'BootSourceOverrideEnabled': 'Once'}} + response = mock.MagicMock(spec=requests.Response) + response.status_code = http_client.FORBIDDEN + response.message = "boom" + mock__op.return_value = response + mock__op.side_effect = \ + exceptions.HTTPError(method='PATCH', + url=target_uri, + response=response) + self.assertRaises(exceptions.HTTPError, self.conn._etag_handler, + 'PATCH', target_uri, data, + self.headers, + blocking=False, timeout=60) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/sushy/tests/unit/test_main.py new/sushy-4.4.0/sushy/tests/unit/test_main.py --- old/sushy-4.3.0/sushy/tests/unit/test_main.py 2022-08-26 19:55:50.000000000 +0200 +++ new/sushy-4.4.0/sushy/tests/unit/test_main.py 2022-11-22 16:25:52.000000000 +0100 @@ -53,7 +53,8 @@ self.root = main.Sushy('http://foo.bar:1234', verify=True, auth=mock_auth) mock_connector.assert_called_once_with( - 'http://foo.bar:1234', verify=True) + 'http://foo.bar:1234', verify=True, server_side_retries=10, + server_side_retries_delay=3) def test__parse_attributes(self): self.root._parse_attributes(self.json_doc) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/sushy/tests/unit/test_utils.py new/sushy-4.4.0/sushy/tests/unit/test_utils.py --- old/sushy-4.3.0/sushy/tests/unit/test_utils.py 2022-08-26 19:55:50.000000000 +0200 +++ new/sushy-4.4.0/sushy/tests/unit/test_utils.py 2022-11-22 16:25:52.000000000 +0100 @@ -43,7 +43,7 @@ def test_int_or_none(self): self.assertEqual(1, utils.int_or_none('1')) - self.assertIsNone(None, utils.int_or_none(None)) + self.assertIsNone(utils.int_or_none(None)) def test_bool_or_none_none(self): self.assertIsNone(utils.bool_or_none(None)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/sushy.egg-info/PKG-INFO new/sushy-4.4.0/sushy.egg-info/PKG-INFO --- old/sushy-4.3.0/sushy.egg-info/PKG-INFO 2022-08-26 19:56:31.000000000 +0200 +++ new/sushy-4.4.0/sushy.egg-info/PKG-INFO 2022-11-22 16:26:19.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: sushy -Version: 4.3.0 +Version: 4.4.0 Summary: Sushy is a small Python library to communicate with Redfish based systems Home-page: https://docs.openstack.org/sushy/latest/ Author: OpenStack diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/sushy.egg-info/SOURCES.txt new/sushy-4.4.0/sushy.egg-info/SOURCES.txt --- old/sushy-4.3.0/sushy.egg-info/SOURCES.txt 2022-08-26 19:56:31.000000000 +0200 +++ new/sushy-4.4.0/sushy.egg-info/SOURCES.txt 2022-11-22 16:26:19.000000000 +0100 @@ -67,6 +67,7 @@ releasenotes/notes/certificate-collection-acc67488c240274c.yaml releasenotes/notes/change-bootdev-smc-ab30317eaf5c37d9.yaml releasenotes/notes/change-vmedia-write-protected-attr-586370a552288801.yaml +releasenotes/notes/config-server-side-retries-d16824019bd709a2.yaml releasenotes/notes/decouple-boot-params-c75e80f5951abb12.yaml releasenotes/notes/deprecate-system-leds-f1a72422c53d281e.yaml releasenotes/notes/disable-conn-pooling-3456782afe56ac94.yaml @@ -101,6 +102,7 @@ releasenotes/notes/get-retry-9ca311caf8a0b7bb.yaml releasenotes/notes/handle-basic-auth-access-errors-393b368b31f5cad2.yaml releasenotes/notes/health_literals_change-0e3fc0c439b765e3.yaml +releasenotes/notes/increase-server-retries-5f11edde8ee0b461.yaml releasenotes/notes/indicator-led-mappings-e7b34da03f6abb06.yaml releasenotes/notes/lazily-load-registries-0e9441e435c2471d.yaml releasenotes/notes/make-leds-settable-c82cb513de0171f5.yaml @@ -113,6 +115,7 @@ releasenotes/notes/reauthentication-session-fallback-failure-fixes-4f0dcfdad1afd2d7.yaml releasenotes/notes/redfish-response-log-294f3f10b770e356.yaml releasenotes/notes/refactor-taskmonitor-update-volume-ba99380188395852.yaml +releasenotes/notes/releasenote-d7138d1e1d414632.yaml releasenotes/notes/remove-deprecated-task-monitors-58c505d43e1fa6a7.yaml releasenotes/notes/retry-if-transferprototype-missing-9cae57f3ecf470a9.yaml releasenotes/notes/secure-boot-76c5b80371ea85d1.yaml @@ -142,6 +145,7 @@ releasenotes/source/wallaby.rst releasenotes/source/xena.rst releasenotes/source/yoga.rst +releasenotes/source/zed.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder sushy/__init__.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/sushy.egg-info/pbr.json new/sushy-4.4.0/sushy.egg-info/pbr.json --- old/sushy-4.3.0/sushy.egg-info/pbr.json 2022-08-26 19:56:31.000000000 +0200 +++ new/sushy-4.4.0/sushy.egg-info/pbr.json 2022-11-22 16:26:19.000000000 +0100 @@ -1 +1 @@ -{"git_version": "2c96ab8", "is_release": true} \ No newline at end of file +{"git_version": "56efdee", "is_release": true} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/sushy-4.3.0/zuul.d/project.yaml new/sushy-4.4.0/zuul.d/project.yaml --- old/sushy-4.3.0/zuul.d/project.yaml 2022-08-26 19:55:50.000000000 +0200 +++ new/sushy-4.4.0/zuul.d/project.yaml 2022-11-22 16:25:52.000000000 +0100 @@ -2,7 +2,7 @@ templates: - check-requirements - openstack-cover-jobs - - openstack-python3-zed-jobs + - openstack-python3-antelope-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 check: