From d9001a791b2e70edbb4559c334a56871d29709ba Mon Sep 17 00:00:00 2001 From: Madhav Date: Wed, 8 Aug 2018 03:40:00 -0400 Subject: [PATCH 1/3] support for firmware bundles. Initial commit --- CHANGELOG.md | 8 +++++++- endpoints-support.md | 2 +- examples/firmware_bundles.py | 7 +++++-- hpOneView/connection.py | 7 ++++--- hpOneView/resources/resource.py | 4 ++-- .../resources/settings/firmware_bundles.py | 5 +++-- .../resources/settings/test_firmware_bundles.py | 2 +- tests/unit/resources/test_resource.py | 4 ++-- tests/unit/test_connection.py | 17 +++++++++++++---- 9 files changed, 38 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eac8fe11..80228c54 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ -# 4.7.1 +# 4.7.1 (Unreleased) +#### Notes +Extends support of the SDK to OneView Rest API version 600 (OneView v4.0). + +#### Features supported with current release: +- Firmware Bundle + #### Bug fixes - [#364] (https://github.com/HewlettPackard/python-hpOneView/issues/364) Bug in index_resources.get_all() diff --git a/endpoints-support.md b/endpoints-support.md index d1e7df72..901460e5 100755 --- a/endpoints-support.md +++ b/endpoints-support.md @@ -130,7 +130,7 @@ |/rest/fcoe-networks/{id} | PUT | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |/rest/fcoe-networks/{id} | DELETE | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | **Firmware Bundles** | -|/rest/firmware-bundles | POST | :white_check_mark: | :white_check_mark: | :white_check_mark: | +|/rest/firmware-bundles | POST | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | **Firmware Drivers** | |/rest/firmware-drivers | GET | :white_check_mark: | :white_check_mark: | :white_check_mark: | |/rest/firmware-drivers | POST | :white_check_mark: | :white_check_mark: | :white_check_mark: | diff --git a/examples/firmware_bundles.py b/examples/firmware_bundles.py index 202d2ca0..c47bfe23 100644 --- a/examples/firmware_bundles.py +++ b/examples/firmware_bundles.py @@ -35,7 +35,10 @@ } # To run this example you must define a path to a valid file -firmware_path = "" +firmware_path = "/home/madhav/SPP2017072.2017_0921.4.iso" + +# Use the below option to specify additional request headers as required +custom_headers = {'initialScopeUris': '/rest/scopes/bf3e77e3-3248-41b3-aaee-5d83b6ac4b49'} # Try load config from a file (if there is a config file) config = try_load_from_file(config) @@ -43,6 +46,6 @@ # Upload a firmware bundle print("\nUpload a firmware bundle") -firmware_bundle_information = oneview_client.firmware_bundles.upload(file_path=firmware_path) +firmware_bundle_information = oneview_client.firmware_bundles.upload(file_path=firmware_path, custom_headers=custom_headers) print("\n Upload successful! Firmware information returned: \n") pprint(firmware_bundle_information) diff --git a/hpOneView/connection.py b/hpOneView/connection.py index 99c420cb..d2115963 100644 --- a/hpOneView/connection.py +++ b/hpOneView/connection.py @@ -271,8 +271,8 @@ def encode_multipart_formdata(self, fields, files, baseName, verbose=False): fin.close() return content_type - def post_multipart_with_response_handling(self, uri, file_path, baseName): - resp, body = self.post_multipart(uri, None, file_path, baseName) + def post_multipart_with_response_handling(self, uri, file_path, custom_headers, baseName): + resp, body = self.post_multipart(uri, None, file_path, custom_headers, baseName) if resp.status == 202: task = self.__get_task_from_response(resp, body) @@ -283,7 +283,7 @@ def post_multipart_with_response_handling(self, uri, file_path, baseName): return None, body - def post_multipart(self, uri, fields, files, baseName, verbose=False): + def post_multipart(self, uri, fields, files, custom_headers, baseName, verbose=False): content_type = self.encode_multipart_formdata(fields, files, baseName, verbose) inputfile = self._open(files + '.b64', 'rb') @@ -295,6 +295,7 @@ def post_multipart(self, uri, fields, files, baseName, verbose=False): conn.connect() conn.putrequest('POST', uri) conn.putheader('uploadfilename', baseName) + conn.putheader('initialScopeUris', custom_headers['initialScopeUris']) conn.putheader('auth', self._headers['auth']) conn.putheader('Content-Type', content_type) totalSize = os.path.getsize(files + '.b64') diff --git a/hpOneView/resources/resource.py b/hpOneView/resources/resource.py index f6f0064d..68a83ff6 100755 --- a/hpOneView/resources/resource.py +++ b/hpOneView/resources/resource.py @@ -1209,7 +1209,7 @@ def create(self, resource, uri=None, timeout=-1, custom_headers=None, default_va return self.__do_post(uri, resource, timeout, custom_headers) - def upload(self, file_path, uri=None, timeout=-1): + def upload(self, file_path, uri=None, custom_headers=None, timeout=-1): """ Makes a multipart request. @@ -1229,7 +1229,7 @@ def upload(self, file_path, uri=None, timeout=-1): uri = self._uri upload_file_name = os.path.basename(file_path) - task, entity = self._connection.post_multipart_with_response_handling(uri, file_path, upload_file_name) + task, entity = self._connection.post_multipart_with_response_handling(uri, file_path, custom_headers, upload_file_name) if not task: return entity diff --git a/hpOneView/resources/settings/firmware_bundles.py b/hpOneView/resources/settings/firmware_bundles.py index e3b12506..77d03077 100644 --- a/hpOneView/resources/settings/firmware_bundles.py +++ b/hpOneView/resources/settings/firmware_bundles.py @@ -45,7 +45,7 @@ def __init__(self, con): self._connection = con self._client = ResourceClient(con, self.URI) - def upload(self, file_path, timeout=-1): + def upload(self, file_path, custom_headers=None, timeout=-1): """ Upload an SPP ISO image file or a hotfix file to the appliance. The API supports upload of one hotfix at a time into the system. @@ -59,4 +59,5 @@ def upload(self, file_path, timeout=-1): Returns: dict: Information about the updated firmware bundle. """ - return self._client.upload(file_path, timeout=timeout) +# custom_headers = { 'initialScopeUris': '/rest/scopes/bf3e77e3-3248-41b3-aaee-5d83b6ac4b49'} + return self._client.upload(file_path, custom_headers=custom_headers, timeout=timeout) diff --git a/tests/unit/resources/settings/test_firmware_bundles.py b/tests/unit/resources/settings/test_firmware_bundles.py index 15a5c3d0..5e61b888 100644 --- a/tests/unit/resources/settings/test_firmware_bundles.py +++ b/tests/unit/resources/settings/test_firmware_bundles.py @@ -42,4 +42,4 @@ def test_upload(self, mock_upload): self._firmware_bundles.upload(firmware_path) - mock_upload.assert_called_once_with(firmware_path, timeout=-1) + mock_upload.assert_called_once_with(firmware_path, custom_headers=None, timeout=-1) diff --git a/tests/unit/resources/test_resource.py b/tests/unit/resources/test_resource.py index 94b85448..508c944b 100644 --- a/tests/unit/resources/test_resource.py +++ b/tests/unit/resources/test_resource.py @@ -1217,7 +1217,7 @@ def test_upload_should_call_post_multipart(self, mock_post_multipart): self.resource_client.upload(filepath, uri) - mock_post_multipart.assert_called_once_with(uri, filepath, 'SPPgen9snap6.2015_0405.81.iso') + mock_post_multipart.assert_called_once_with(uri, filepath, None, 'SPPgen9snap6.2015_0405.81.iso') @mock.patch.object(connection, 'post_multipart_with_response_handling') def test_upload_should_call_post_multipart_with_resource_uri_when_not_uri_provided(self, mock_post_multipart): @@ -1226,7 +1226,7 @@ def test_upload_should_call_post_multipart_with_resource_uri_when_not_uri_provid self.resource_client.upload(filepath) - mock_post_multipart.assert_called_once_with('/rest/testuri', mock.ANY, mock.ANY) + mock_post_multipart.assert_called_once_with('/rest/testuri', mock.ANY, mock.ANY, mock.ANY) @mock.patch.object(connection, 'post_multipart_with_response_handling') @mock.patch.object(TaskMonitor, 'wait_for_task') diff --git a/tests/unit/test_connection.py b/tests/unit/test_connection.py index 17877d8a..303dee87 100644 --- a/tests/unit/test_connection.py +++ b/tests/unit/test_connection.py @@ -629,6 +629,7 @@ def test_post_multipart_should_put_request(self, mock_rm, mock_path_size, mock_c self.connection.post_multipart(uri='/rest/resources/', fields=None, files="/a/path/filename.zip", + custom_headers={'initialScopeUris': '/rest/scopes/fake'}, baseName="archive.zip") internal_conn = self.connection.get_connection.return_value @@ -646,10 +647,12 @@ def test_post_multipart_should_put_headers(self, mock_rm, mock_path_size, mock_c self.connection.post_multipart(uri='/rest/resources/', fields=None, files="/a/path/filename.zip", + custom_headers={'initialScopeUris': '/rest/scopes/fake'}, baseName="archive.zip") expected_putheader_calls = [ call('uploadfilename', 'archive.zip'), + call(u'initialScopeUris', '/rest/scopes/fake'), call('auth', 'LTIxNjUzMjc0OTUzzHoF7eEkZLEUWVA-fuOZP4VGA3U8e67E'), call('Content-Type', 'multipart/form-data; boundary=----------ThIs_Is_tHe_bouNdaRY_$'), call('Content-Length', 2621440), @@ -669,6 +672,7 @@ def test_post_multipart_should_read_file_in_chunks_of_1mb(self, mock_rm, mock_pa self.connection.post_multipart(uri='/rest/resources/', fields=None, files="/a/path/filename.zip", + custom_headers={'initialScopeUris': '/rest/scopes/fake'}, baseName="archive.zip") expected_mmap_read_calls = [ @@ -689,6 +693,7 @@ def test_post_multipart_should_send_file_in_chuncks_of_1mb(self, mock_rm, mock_p self.connection.post_multipart(uri='/rest/resources/', fields=None, files="/a/path/filename.zip", + custom_headers={'initialScopeUris': '/rest/scopes/fake'}, baseName="archive.zip") expected_conn_send_calls = [ @@ -710,6 +715,7 @@ def test_post_multipart_should_remove_temp_encoded_file(self, mock_rm, mock_path self.connection.post_multipart(uri='/rest/resources/', fields=None, files="/a/path/filename.zip", + custom_headers={'initialScopeUris': '/rest/scopes/fake'}, baseName="archive.zip") mock_rm.assert_called_once_with('/a/path/filename.zip.b64') @@ -727,6 +733,7 @@ def test_post_multipart_should_raise_exception_when_response_status_400(self, mo self.connection.post_multipart(uri='/rest/resources/', fields=None, files="/a/path/filename.zip", + custom_headers={'initialScopeUris': '/rest/scopes/fake'}, baseName="archive.zip") except HPOneViewException as e: self.assertEqual(e.msg, "An error occurred.") @@ -745,6 +752,7 @@ def test_post_multipart_should_return_response_and_body_when_response_status_200 response, body = self.connection.post_multipart(uri='/rest/resources/', fields=None, files="/a/path/filename.zip", + custom_headers={'initialScopeUris': '/rest/scopes/fake'}, baseName="archive.zip") self.assertEqual(body, self.expected_response_body) @@ -764,6 +772,7 @@ def test_post_multipart_should_handle_json_load_exception(self, mock_json_loads, response, body = self.connection.post_multipart(uri='/rest/resources/', fields=None, files="/a/path/filename.zip", + custom_headers={'initialScopeUris': '/rest/scopes/fake'}, baseName="archive.zip") self.assertTrue(body) @@ -775,7 +784,7 @@ def test_post_multipart_with_response_handling_when_status_202_without_task(self mock_response.getheader.return_value = None mock_post_multipart.return_value = mock_response, "content" - task, body = self.connection.post_multipart_with_response_handling("uri", "filepath", "basename") + task, body = self.connection.post_multipart_with_response_handling("uri", "filepath", "custom_headers", "basename") self.assertFalse(task) self.assertEqual(body, "content") @@ -789,7 +798,7 @@ def test_post_multipart_with_response_handling_when_status_202_with_task(self, m mock_post_multipart.return_value = mock_response, "content" mock_get.return_value = fake_task - task, body = self.connection.post_multipart_with_response_handling("uri", "filepath", "basename") + task, body = self.connection.post_multipart_with_response_handling("uri", "filepath", "custom_headers", "basename") self.assertEqual(task, fake_task) self.assertEqual(body, "content") @@ -799,7 +808,7 @@ def test_post_multipart_with_response_handling_when_status_200_and_body_is_task( fake_task = {"category": "tasks"} mock_post_multipart.return_value = Mock(status=200), fake_task - task, body = self.connection.post_multipart_with_response_handling("uri", "filepath", "basename") + task, body = self.connection.post_multipart_with_response_handling("uri", "filepath", "custom_headers", "basename") self.assertEqual(task, fake_task) self.assertEqual(body, fake_task) @@ -808,7 +817,7 @@ def test_post_multipart_with_response_handling_when_status_200_and_body_is_task( def test_post_multipart_with_response_handling_when_status_200_and_body_is_not_task(self, mock_post_multipart): mock_post_multipart.return_value = Mock(status=200), "content" - task, body = self.connection.post_multipart_with_response_handling("uri", "filepath", "basename") + task, body = self.connection.post_multipart_with_response_handling("uri", "filepath", "custom_headers", "basename") self.assertFalse(task) self.assertEqual(body, "content") From 0bdc5753dda76626cb6dc50391e90a290ffd1714 Mon Sep 17 00:00:00 2001 From: Madhav Date: Thu, 23 Aug 2018 06:26:56 -0400 Subject: [PATCH 2/3] Incorporating enhancements,review comments --- examples/firmware_bundles.py | 6 +++--- hpOneView/connection.py | 6 +++--- hpOneView/oneview_client.py | 4 +--- hpOneView/resources/resource.py | 4 ++-- hpOneView/resources/settings/firmware_bundles.py | 4 ++-- tests/unit/resources/settings/test_firmware_bundles.py | 2 +- tests/unit/resources/test_resource.py | 2 +- tests/unit/test_oneview_client.py | 9 ++++++--- 8 files changed, 19 insertions(+), 18 deletions(-) diff --git a/examples/firmware_bundles.py b/examples/firmware_bundles.py index c47bfe23..16513706 100644 --- a/examples/firmware_bundles.py +++ b/examples/firmware_bundles.py @@ -35,10 +35,10 @@ } # To run this example you must define a path to a valid file -firmware_path = "/home/madhav/SPP2017072.2017_0921.4.iso" +firmware_path = "" # Use the below option to specify additional request headers as required -custom_headers = {'initialScopeUris': '/rest/scopes/bf3e77e3-3248-41b3-aaee-5d83b6ac4b49'} +custom_headers = {'initialScopeUris': ''} # Try load config from a file (if there is a config file) config = try_load_from_file(config) @@ -48,4 +48,4 @@ print("\nUpload a firmware bundle") firmware_bundle_information = oneview_client.firmware_bundles.upload(file_path=firmware_path, custom_headers=custom_headers) print("\n Upload successful! Firmware information returned: \n") -pprint(firmware_bundle_information) +pprint(firmware_bundle_information['name']) diff --git a/hpOneView/connection.py b/hpOneView/connection.py index d2115963..fcf8f03d 100644 --- a/hpOneView/connection.py +++ b/hpOneView/connection.py @@ -271,8 +271,8 @@ def encode_multipart_formdata(self, fields, files, baseName, verbose=False): fin.close() return content_type - def post_multipart_with_response_handling(self, uri, file_path, custom_headers, baseName): - resp, body = self.post_multipart(uri, None, file_path, custom_headers, baseName) + def post_multipart_with_response_handling(self, uri, file_path, baseName, custom_headers): + resp, body = self.post_multipart(uri, None, file_path, baseName, custom_headers) if resp.status == 202: task = self.__get_task_from_response(resp, body) @@ -283,7 +283,7 @@ def post_multipart_with_response_handling(self, uri, file_path, custom_headers, return None, body - def post_multipart(self, uri, fields, files, custom_headers, baseName, verbose=False): + def post_multipart(self, uri, fields, files, baseName, custom_headers, verbose=False): content_type = self.encode_multipart_formdata(fields, files, baseName, verbose) inputfile = self._open(files + '.b64', 'rb') diff --git a/hpOneView/oneview_client.py b/hpOneView/oneview_client.py index 56c2c342..ec86e03a 100755 --- a/hpOneView/oneview_client.py +++ b/hpOneView/oneview_client.py @@ -905,9 +905,7 @@ def firmware_bundles(self): Returns: FirmwareBundles: """ - if not self.__firmware_bundles: - self.__firmware_bundles = FirmwareBundles(self.__connection) - return self.__firmware_bundles + return FirmwareBundles(self.__connection) @property def uplink_sets(self): diff --git a/hpOneView/resources/resource.py b/hpOneView/resources/resource.py index 68a83ff6..ba2d686e 100755 --- a/hpOneView/resources/resource.py +++ b/hpOneView/resources/resource.py @@ -1209,7 +1209,7 @@ def create(self, resource, uri=None, timeout=-1, custom_headers=None, default_va return self.__do_post(uri, resource, timeout, custom_headers) - def upload(self, file_path, uri=None, custom_headers=None, timeout=-1): + def upload(self, file_path, uri=None, timeout=-1, custom_headers=None): """ Makes a multipart request. @@ -1229,7 +1229,7 @@ def upload(self, file_path, uri=None, custom_headers=None, timeout=-1): uri = self._uri upload_file_name = os.path.basename(file_path) - task, entity = self._connection.post_multipart_with_response_handling(uri, file_path, custom_headers, upload_file_name) + task, entity = self._connection.post_multipart_with_response_handling(uri, file_path, upload_file_name, custom_headers) if not task: return entity diff --git a/hpOneView/resources/settings/firmware_bundles.py b/hpOneView/resources/settings/firmware_bundles.py index 77d03077..2ce9ad57 100644 --- a/hpOneView/resources/settings/firmware_bundles.py +++ b/hpOneView/resources/settings/firmware_bundles.py @@ -45,7 +45,7 @@ def __init__(self, con): self._connection = con self._client = ResourceClient(con, self.URI) - def upload(self, file_path, custom_headers=None, timeout=-1): + def upload(self, file_path, timeout=-1, custom_headers=None,): """ Upload an SPP ISO image file or a hotfix file to the appliance. The API supports upload of one hotfix at a time into the system. @@ -60,4 +60,4 @@ def upload(self, file_path, custom_headers=None, timeout=-1): dict: Information about the updated firmware bundle. """ # custom_headers = { 'initialScopeUris': '/rest/scopes/bf3e77e3-3248-41b3-aaee-5d83b6ac4b49'} - return self._client.upload(file_path, custom_headers=custom_headers, timeout=timeout) + return self._client.upload(file_path, timeout=timeout, custom_headers=custom_headers) diff --git a/tests/unit/resources/settings/test_firmware_bundles.py b/tests/unit/resources/settings/test_firmware_bundles.py index 5e61b888..26b06949 100644 --- a/tests/unit/resources/settings/test_firmware_bundles.py +++ b/tests/unit/resources/settings/test_firmware_bundles.py @@ -42,4 +42,4 @@ def test_upload(self, mock_upload): self._firmware_bundles.upload(firmware_path) - mock_upload.assert_called_once_with(firmware_path, custom_headers=None, timeout=-1) + mock_upload.assert_called_once_with(firmware_path, timeout=-1, custom_headers=None) diff --git a/tests/unit/resources/test_resource.py b/tests/unit/resources/test_resource.py index 508c944b..3ba56fbd 100644 --- a/tests/unit/resources/test_resource.py +++ b/tests/unit/resources/test_resource.py @@ -1217,7 +1217,7 @@ def test_upload_should_call_post_multipart(self, mock_post_multipart): self.resource_client.upload(filepath, uri) - mock_post_multipart.assert_called_once_with(uri, filepath, None, 'SPPgen9snap6.2015_0405.81.iso') + mock_post_multipart.assert_called_once_with(uri, filepath, 'SPPgen9snap6.2015_0405.81.iso', None) @mock.patch.object(connection, 'post_multipart_with_response_handling') def test_upload_should_call_post_multipart_with_resource_uri_when_not_uri_provided(self, mock_post_multipart): diff --git a/tests/unit/test_oneview_client.py b/tests/unit/test_oneview_client.py index 21dfb6c4..5282a9a4 100755 --- a/tests/unit/test_oneview_client.py +++ b/tests/unit/test_oneview_client.py @@ -79,6 +79,7 @@ from hpOneView.resources.settings.appliance_node_information import ApplianceNodeInformation from hpOneView.resources.settings.appliance_time_and_locale_configuration import ApplianceTimeAndLocaleConfiguration from hpOneView.resources.settings.versions import Versions +from hpOneView.resources.settings.firmware_bundles import FirmwareBundles from tests.test_utils import mock_builtin from hpOneView.resources.settings.licenses import Licenses @@ -566,9 +567,11 @@ def test_lazy_loading_firmware_drivers(self): firmware_drivers = self._oneview.firmware_drivers self.assertEqual(firmware_drivers, self._oneview.firmware_drivers) - def test_lazy_loading_firmware_bundles(self): - firmware_bundles = self._oneview.firmware_bundles - self.assertEqual(firmware_bundles, self._oneview.firmware_bundles) + def test_firmware_bundles_has_right_type(self): + self.assertIsInstance(self._oneview.firmware_bundles, FirmwareBundles) + + def test_firmware_bundles_has_value(self): + self.assertIsNotNone(self._oneview.firmware_bundles) def test_migratable_vc_domains_has_right_type(self): self.assertIsInstance(self._oneview.migratable_vc_domains, MigratableVcDomains) From 03b02e037b9b8dbe489788a1d29100eca20a81d8 Mon Sep 17 00:00:00 2001 From: Madhav Date: Mon, 3 Sep 2018 00:26:59 -0400 Subject: [PATCH 3/3] Incorporating review comments --- CHANGELOG.md | 8 ++++++-- hpOneView/connection.py | 2 +- hpOneView/resources/resource.py | 2 ++ hpOneView/resources/settings/firmware_bundles.py | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80228c54..a9a69f67 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ -# 4.7.1 (Unreleased) +# 5.0.0 (Unreleased) #### Notes -Extends support of the SDK to OneView Rest API version 600 (OneView v4.0). +Major release where the resource file is revamped, so that it can be used to instantiate the resource object. +Addition helper methods where necessary to make the sdk more user friendly and to reduce the dependency on hard coded values. + +#### Breaking changes +Resource file now instantiates the object, which is used to directly call the available methods. #### Features supported with current release: - Firmware Bundle diff --git a/hpOneView/connection.py b/hpOneView/connection.py index fcf8f03d..f456cb7a 100644 --- a/hpOneView/connection.py +++ b/hpOneView/connection.py @@ -283,7 +283,7 @@ def post_multipart_with_response_handling(self, uri, file_path, baseName, custom return None, body - def post_multipart(self, uri, fields, files, baseName, custom_headers, verbose=False): + def post_multipart(self, uri, fields, files, baseName, verbose=False, custom_headers=None): content_type = self.encode_multipart_formdata(fields, files, baseName, verbose) inputfile = self._open(files + '.b64', 'rb') diff --git a/hpOneView/resources/resource.py b/hpOneView/resources/resource.py index ba2d686e..8fcb946d 100755 --- a/hpOneView/resources/resource.py +++ b/hpOneView/resources/resource.py @@ -1221,6 +1221,8 @@ def upload(self, file_path, uri=None, timeout=-1, custom_headers=None): timeout: Timeout in seconds. Wait for task completion by default. The timeout does not abort the operation in OneView; it just stops waiting for its completion. + custom_headers: + Allows set specific HTTP headers. Returns: dict: Response body. diff --git a/hpOneView/resources/settings/firmware_bundles.py b/hpOneView/resources/settings/firmware_bundles.py index 2ce9ad57..0ffee7e5 100644 --- a/hpOneView/resources/settings/firmware_bundles.py +++ b/hpOneView/resources/settings/firmware_bundles.py @@ -45,7 +45,7 @@ def __init__(self, con): self._connection = con self._client = ResourceClient(con, self.URI) - def upload(self, file_path, timeout=-1, custom_headers=None,): + def upload(self, file_path, timeout=-1, custom_headers=None): """ Upload an SPP ISO image file or a hotfix file to the appliance. The API supports upload of one hotfix at a time into the system.