diff --git a/CHANGELOG.md b/CHANGELOG.md index eac8fe11..a9a69f67 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,14 @@ -# 4.7.1 +# 5.0.0 (Unreleased) +#### Notes +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 + #### 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..16513706 100644 --- a/examples/firmware_bundles.py +++ b/examples/firmware_bundles.py @@ -37,12 +37,15 @@ # To run this example you must define a path to a valid file firmware_path = "" +# Use the below option to specify additional request headers as required +custom_headers = {'initialScopeUris': ''} + # Try load config from a file (if there is a config file) config = try_load_from_file(config) oneview_client = OneViewClient(config) # 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) +pprint(firmware_bundle_information['name']) diff --git a/hpOneView/connection.py b/hpOneView/connection.py index 99c420cb..f456cb7a 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, 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, baseName): return None, body - def post_multipart(self, uri, fields, files, baseName, 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') @@ -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/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 f6f0064d..8fcb946d 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, timeout=-1, custom_headers=None): """ Makes a multipart request. @@ -1221,6 +1221,8 @@ def upload(self, file_path, uri=None, timeout=-1): 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. @@ -1229,7 +1231,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, 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 e3b12506..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): + 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. @@ -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, 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 15a5c3d0..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, 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 94b85448..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, '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): @@ -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") 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)