Repository: HewlettPackard/oneview-python

Issue:
MISSING_JSON_TYPE error code is thrown when attempting to restore the appliance
For OneView's X-API-VERSION: **1200** the DTO type for restore is `RESTOREV1000`. I believe this is causing the below error message when attempting to restore a 5.00 OneView appliance with the default X-API-VERSION.

```hpOneView.exceptions.HPOneViewException: ('The "type" field is missing from the JSON.', {'errorCode': 'MISSING_JSON_TYPE', 'message': 'The "type" field is missing from the JSON.', 'details': 'The "type" field was not included in the JSON input of this request, so the request is invalid.', 'recommendedActions': ['Include the "type" field in the JSON and try the request again.'], 'errorSource': None, 'nestedErrors': [], 'data': {}})```

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cc1d1195..2bf3ad10 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,14 @@
 # 5.2.1 (unreleased)
+#### Notes
+Extends support of the SDK to OneView REST API version 800, 1000, 1200 and 1600.
 Added code to handle login acknowledgement.
 
+#### Features supported
+- Restores
+
 #### Bug fixes & Enhancements
 - #21 In correct endpoint passed when uploading a downloaded appliance backup
+- #23 MISSING_JSON_TYPE error code is thrown when attempting to restore the appliance
 - #58 loginMsgAck is not handled in python code
 - #60 SPT type for api1600 should be ServerProfileTemplateV8
 
diff --git a/endpoints-support.md b/endpoints-support.md
index f3184324..7c594fa2 100755
--- a/endpoints-support.md
+++ b/endpoints-support.md
@@ -205,6 +205,11 @@
 |     **OS Deployment Plans**
 |<sub>/rest/os-deployment-plans/</sub>                                                    | GET      |  :white_check_mark:   | :white_check_mark:   | :white_check_mark:   |
 |<sub>/rest/os-deployment-plans/{id}</sub>                                                | GET      |  :white_check_mark:   | :white_check_mark:   | :white_check_mark:   |
+|     **Restores**
+|<sub>/rest/restores</sub>                                                                | GET      |  :white_check_mark:   | :white_check_mark:   | :white_check_mark:   | :white_check_mark:   |
+|<sub>/rest/restores/{id}</sub>                                                           | GET      |  :white_check_mark:   | :white_check_mark:   | :white_check_mark:   | :white_check_mark:   |
+|<sub>/rest/restores</sub>                                                                | POST     |  :white_check_mark:   | :white_check_mark:   | :white_check_mark:   | :white_check_mark:   |
+|<sub>/rest/restores/failure</sub>                                                        | GET      |  :white_check_mark:   | :white_check_mark:   | :white_check_mark:   | :white_check_mark:   |
 |     **SAS Interconnect Types**
 |<sub>/rest/sas-interconnect-types</sub>                                                  | GET      |  :white_check_mark:   | :white_check_mark:   | :white_check_mark:   |
 |<sub>/rest/sas-interconnect-types/{id}</sub>                                             | GET      |  :white_check_mark:   | :white_check_mark:   | :white_check_mark:   |
diff --git a/examples/restores.py b/examples/restores.py
index 480e971a..2e0fc33f 100644
--- a/examples/restores.py
+++ b/examples/restores.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 ###
-# (C) Copyright [2019] Hewlett Packard Enterprise Development LP
+# (C) Copyright [2020] Hewlett Packard Enterprise Development LP
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -20,19 +20,19 @@ from hpOneView.oneview_client import OneViewClient
 from config_loader import try_load_from_file
 
 config = {
-    "ip": "172.16.102.59",
+    "ip": "",
     "credentials": {
-        "userName": "administrator",
-        "password": "123456"
+        "userName": "",
+        "password": ""
     }
 }
 
 # Try load config from a file (if there is a config file)
 config = try_load_from_file(config)
-
 oneview_client = OneViewClient(config)
+restores = oneview_client.restores
 
-# A Uri for a backup to restore must be set to run this example
+# A uri for a backup to restore must be set to run this example
 uri_of_backup_to_restore = "/rest/backups/example_backup_2017-04-25_195515"
 
 # Start a restore
@@ -40,25 +40,26 @@ print("Starting a restore")
 options = {
     "uriOfBackupToRestore": uri_of_backup_to_restore
 }
-restore = oneview_client.restores.restore(options)
-pprint(restore)
+restore = restores.restore(options)
+pprint(restore.data)
 
 # Get all restores
 print("Get all Restores")
-restores = oneview_client.restores.get_all()
-pprint(restores)
+restore_all = restores.get_all()
+for restr in restore_all:
+    print('  - {}'.format(restr['hostName']))
 
-# Get by
+# Get by hostname
 print("\nGet a Restore by hostName equals to '{}'".format(restore['hostName']))
-restore_by_host_name = oneview_client.restores.get_by("hostName", restore['hostName'])
+restore_by_host_name = restores.get_by("hostName", restore['hostName'])
 pprint(restore_by_host_name)
 
 # Get by URI
 print("\nGet a Restore by URI")
-restore_by_uri = oneview_client.restores.get(restore['uri'])
-pprint(restore_by_uri)
+restore_by_uri = restores.get_by_uri(restore['uri'])
+pprint(restore_by_uri.data)
 
 # Get Restore Failure
 print("\nRetrieving Restore Failure")
-failures = oneview_client.restores.get_failure()
+failures = restores.get_failure()
 pprint(failures)
diff --git a/hpOneView/oneview_client.py b/hpOneView/oneview_client.py
index d0cd445f..d3d3552e 100755
--- a/hpOneView/oneview_client.py
+++ b/hpOneView/oneview_client.py
@@ -387,9 +387,7 @@ class OneViewClient(object):
         Returns:
             Restores:
         """
-        if not self.__restores:
-            self.__restores = Restores(self.__connection)
-        return self.__restores
+        return Restores(self.__connection)
 
     @property
     def scopes(self):
diff --git a/hpOneView/resources/settings/restores.py b/hpOneView/resources/settings/restores.py
index 018be7c3..404de571 100644
--- a/hpOneView/resources/settings/restores.py
+++ b/hpOneView/resources/settings/restores.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 ###
-# (C) Copyright [2019] Hewlett Packard Enterprise Development LP
+# (C) Copyright [2020] Hewlett Packard Enterprise Development LP
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -24,59 +24,24 @@ from future import standard_library
 
 standard_library.install_aliases()
 
-from hpOneView.resources.resource import ResourceClient
+from hpOneView.resources.resource import Resource
 
 
-class Restores(object):
+class Restores(Resource):
     """
     Restore API client for initiate a restore of an appliance and to get the status of the restore operation.
     """
     URI = '/rest/restores'
 
     DEFAULT_VALUES = {
-        '200': {"type": "RESTORE"},
-        '300': {"type": "RESTORE"}
+        '800': {"type": "RESTORE"},
+        '1000': {"type": "RESTOREV1000"},
+        '1200': {"type": "RESTOREV1000"},
+        '1600': {"type": "RESTOREV1000"}
     }
 
-    def __init__(self, con):
-        self._client = ResourceClient(con, self.URI)
-
-    def get_all(self):
-        """
-        Retrieve the status of any current appliance restore.
-
-        Returns:
-            list: A collection of restore status, but there will be at most one restore status. The status for
-            the last restore will be returned if there has been a restore.
-        """
-        return self._client.get_all()
-
-    def get(self, id_or_uri):
-        """
-        Retrieves the status of the specified restore operation.
-
-        Args:
-            id_or_uri: ID or URI of the Restore.
-
-        Returns:
-            dict: Restore
-        """
-        return self._client.get(id_or_uri)
-
-    def get_by(self, field, value):
-        """
-        Gets all Restores that match the filter.
-
-        The search is case-insensitive.
-
-        Args:
-            field: Field name to filter.
-            value: Value to filter.
-
-        Returns:
-            list: A list of Restores.
-        """
-        return self._client.get_by(field, value)
+    def __init__(self, connection, data=None):
+        super(Restores, self).__init__(connection, data)
 
     def get_failure(self):
         """
@@ -85,12 +50,11 @@ class Restores(object):
         The restore result remains valid until a user logs in. After a user logs in, the restore result will be reset.
         This rest request will return only the valid result after restore has completed and before a user logs in.
 
-
         Returns:
             dict: Restore Result
         """
-        uri = self.URI + '/failure'
-        return self._client.get(uri)
+        failure_uri = "{0}/failure".format(self.URI)
+        return self._helper.do_get(failure_uri)
 
     def restore(self, resource, timeout=-1):
         """
@@ -107,4 +71,4 @@ class Restores(object):
             dict: Restore.
 
         """
-        return self._client.create(resource, timeout=timeout, default_values=self.DEFAULT_VALUES)
+        return super(Restores, self).create(resource, timeout=timeout)


Test patch:
diff --git a/tests/unit/resources/fc_sans/test_managed_sans.py b/tests/unit/resources/fc_sans/test_managed_sans.py
index 4c36d380..f45b4b34 100644
--- a/tests/unit/resources/fc_sans/test_managed_sans.py
+++ b/tests/unit/resources/fc_sans/test_managed_sans.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 ###
-# (C) Copyright [2019] Hewlett Packard Enterprise Development LP
+# (C) Copyright [2020] Hewlett Packard Enterprise Development LP
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -115,3 +115,9 @@ class ManagedSANsTest(TestCase):
 
         expected_uri = '{}/issues/'.format(self._resource.data["uri"])
         mock_create_report.assert_called_once_with(expected_uri, -1)
+
+    @mock.patch.object(ResourceHelper, 'do_get')
+    def test_get_wwn_called_once(self, mock_get):
+        uri = '/rest/fc-sans/managed-sans?locate=test'
+        self._resource.get_wwn('test')
+        mock_get.assert_called_once_with(uri)
diff --git a/tests/unit/resources/settings/test_restores.py b/tests/unit/resources/settings/test_restores.py
index 3fb4dfdd..9640c37b 100644
--- a/tests/unit/resources/settings/test_restores.py
+++ b/tests/unit/resources/settings/test_restores.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 ###
-# (C) Copyright [2019] Hewlett Packard Enterprise Development LP
+# (C) Copyright [2020] Hewlett Packard Enterprise Development LP
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -20,70 +20,26 @@ from unittest import TestCase
 import mock
 
 from hpOneView.connection import connection
-from hpOneView.resources.resource import ResourceClient
+from hpOneView.resources.resource import Resource, ResourceHelper
 from hpOneView.resources.settings.restores import Restores
 
 
 class RestoresTest(TestCase):
     def setUp(self):
-        oneview_connection = connection('127.0.0.1')
-        self.resource = Restores(oneview_connection)
+        self.host = '127.0.0.1'
+        self.connection = connection(self.host)
+        self.resource = Restores(self.connection)
 
-        self.restores = [
-            {"backupIdToRestore": "example_backup_2017-03-06_023131"},
-            {"backupIdToRestore": "example_backup_2017-04-06_023131"}
-        ]
-
-    @mock.patch.object(ResourceClient, 'get_all')
-    def test_get_all(self, mock_get_all):
-        self.resource.get_all()
-        mock_get_all.assert_called_once()
-
-    @mock.patch.object(ResourceClient, 'get_all')
-    def test_get_by_restores_when_found(self, mock_get_all):
-        mock_get_all.return_value = self.restores
-
-        expected_result = [self.restores[0]]
-
-        result = self.resource.get_by("backupIdToRestore", "example_backup_2017-03-06_023131")
-
-        mock_get_all.assert_called_once_with(filter='"backupIdToRestore=\'example_backup_2017-03-06_023131\'"',
-                                             uri='/rest/restores')
-        self.assertEqual(result, expected_result)
-
-    @mock.patch.object(ResourceClient, 'get_all')
-    def test_get_by_should_return_empty_list_when_not_found(self, mock_get_all):
-        mock_get_all.return_value = []
-
-        result = self.resource.get_by("backupIdToRestore", "example_backup_2017-03-06_023131")
-
-        self.assertEqual(result, [])
-
-    @mock.patch.object(ResourceClient, 'get')
-    def test_get_should_be_called_once(self, mock_get):
-        self.resource.get('3518be0e-17c1-4189-8f81-83f3724f6155')
-
-        mock_get.assert_called_once_with('3518be0e-17c1-4189-8f81-83f3724f6155')
-
-    @mock.patch.object(ResourceClient, 'get')
-    def test_get_with_uri_should_be_called_once(self, mock_get):
-        uri = '/rest/restores/3518be0e-17c1-4189-8f81-83f3724f6155'
-        self.resource.get(uri)
-
-        mock_get.assert_called_once_with(uri)
-
-    @mock.patch.object(ResourceClient, 'get')
+    @mock.patch.object(ResourceHelper, 'do_get')
     def test_get_failure_should_be_called_once(self, mock_get):
         uri = '/rest/restores/failure'
         self.resource.get_failure()
-
         mock_get.assert_called_once_with(uri)
 
-    @mock.patch.object(ResourceClient, 'create')
+    @mock.patch.object(Resource, 'create')
     def test_restore_should_be_called_once(self, mock_create):
         restore = {
             "uriOfBackupToRestore": "/rest/backups/example_backup_2014-03-06_023131"
         }
         self.resource.restore(restore)
-
-        mock_create.assert_called_once_with(restore, timeout=-1, default_values=self.resource.DEFAULT_VALUES)
+        mock_create.assert_called_once_with(restore, timeout=-1)
diff --git a/tests/unit/test_oneview_client.py b/tests/unit/test_oneview_client.py
index 3fe59e6a..5818172b 100755
--- a/tests/unit/test_oneview_client.py
+++ b/tests/unit/test_oneview_client.py
@@ -713,9 +713,9 @@ class OneViewClientTest(unittest.TestCase):
     def test_restores_has_right_type(self):
         self.assertIsInstance(self._oneview.restores, Restores)
 
-    def test_lazy_loading_restores(self):
-        copy_restores = self._oneview.restores
-        self.assertEqual(copy_restores, self._oneview.restores)
+    def test_restores_client(self):
+        restores = self._oneview.restores
+        self.assertNotEqual(restores, self._oneview.restores)
 
     def test_scopes_has_right_type(self):
         self.assertIsInstance(self._oneview.scopes, Scopes)