Add support for loading resources from archive file
As Redfish Message registries can be reside inside archives, added support to load resource from JSON files in archive. Currently supporting only ZIP archives, support for other archive types can be added as need arises or specification is clarified. The Redfish specification does not detail which types of archives need to be supported, but gives ZIP as an example. Change-Id: I3609df39c68f2149c1ff1a6818af7168bbd02df0 Story: 2001791 Task: 23062
This commit is contained in:
parent
28ee59fd79
commit
878a32e07f
|
@ -57,6 +57,10 @@ class InvalidParameterValueError(SushyError):
|
|||
'Valid values are: %(valid_values)s')
|
||||
|
||||
|
||||
class ArchiveParsingError(SushyError):
|
||||
message = 'Failed parsing archive "%(path)s": %(error)s'
|
||||
|
||||
|
||||
class HTTPError(SushyError):
|
||||
"""Basic exception for HTTP errors"""
|
||||
|
||||
|
|
|
@ -16,7 +16,10 @@
|
|||
import abc
|
||||
import collections
|
||||
import copy
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import zipfile
|
||||
|
||||
import six
|
||||
|
||||
|
@ -244,13 +247,69 @@ class MappedField(Field):
|
|||
adapter=mapping.get)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class AbstractJsonReader(object):
|
||||
|
||||
def set_connection(self, connector, path):
|
||||
"""Sets mandatory connection parameters
|
||||
|
||||
:param connector: A Connector instance
|
||||
:param path: path of the resource
|
||||
"""
|
||||
self._conn = connector
|
||||
self._path = path
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_json(self):
|
||||
"""Based on data source get data and parse to JSON"""
|
||||
|
||||
|
||||
class JsonFileReader(AbstractJsonReader):
|
||||
"""Gets the data from JSON file given by path"""
|
||||
|
||||
def get_json(self):
|
||||
"""Gets JSON file from URI directly"""
|
||||
return self._conn.get(path=self._path).json()
|
||||
|
||||
|
||||
class JsonArchiveReader(AbstractJsonReader):
|
||||
"""Gets the data from JSON file in archive"""
|
||||
|
||||
def __init__(self, archive_file):
|
||||
"""Initializes the reader
|
||||
|
||||
:param archive_file: file name of JSON file in archive
|
||||
"""
|
||||
self._archive_file = archive_file
|
||||
|
||||
def get_json(self):
|
||||
"""Gets JSON file from archive. Currently supporting ZIP only"""
|
||||
|
||||
data = self._conn.get(path=self._path)
|
||||
if data.headers.get('content-type') == 'application/zip':
|
||||
try:
|
||||
archive = zipfile.ZipFile(io.BytesIO(data.content))
|
||||
return json.loads(archive.read(self._archive_file)
|
||||
.decode(encoding='utf-8'))
|
||||
except (zipfile.BadZipfile, ValueError) as e:
|
||||
raise exceptions.ArchiveParsingError(
|
||||
path=self._path, error=e)
|
||||
else:
|
||||
LOG.error('Support for %(type)s not implemented',
|
||||
{'type': data.headers['content-type']})
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class ResourceBase(object):
|
||||
|
||||
redfish_version = None
|
||||
"""The Redfish version"""
|
||||
|
||||
def __init__(self, connector, path='', redfish_version=None):
|
||||
def __init__(self,
|
||||
connector,
|
||||
path='',
|
||||
redfish_version=None,
|
||||
reader=JsonFileReader()):
|
||||
"""A class representing the base of any Redfish resource
|
||||
|
||||
Invokes the ``refresh()`` method of resource for the first
|
||||
|
@ -259,6 +318,8 @@ class ResourceBase(object):
|
|||
:param path: sub-URI path to the resource.
|
||||
:param redfish_version: The version of Redfish. Used to construct
|
||||
the object according to schema of the given version.
|
||||
:param reader: Reader to use to fetch JSON data. Defaults to
|
||||
JsonFileReader
|
||||
"""
|
||||
self._conn = connector
|
||||
self._path = path
|
||||
|
@ -269,6 +330,9 @@ class ResourceBase(object):
|
|||
# attribute values are fetched.
|
||||
self._is_stale = True
|
||||
|
||||
reader.set_connection(connector, path)
|
||||
self._reader = reader
|
||||
|
||||
self.refresh()
|
||||
|
||||
def _parse_attributes(self):
|
||||
|
@ -299,7 +363,8 @@ class ResourceBase(object):
|
|||
if not self._is_stale and not force:
|
||||
return
|
||||
|
||||
self._json = self._conn.get(path=self._path).json()
|
||||
self._json = self._reader.get_json()
|
||||
|
||||
LOG.debug('Received representation of %(type)s %(path)s: %(json)s',
|
||||
{'type': self.__class__.__name__,
|
||||
'path': self._path, 'json': self._json})
|
||||
|
|
Binary file not shown.
|
@ -14,13 +14,15 @@
|
|||
# under the License.
|
||||
|
||||
import copy
|
||||
|
||||
import io
|
||||
import mock
|
||||
|
||||
from six.moves import http_client
|
||||
|
||||
from sushy import exceptions
|
||||
from sushy.resources import base as resource_base
|
||||
from sushy.tests.unit import base
|
||||
import zipfile
|
||||
|
||||
|
||||
class BaseResource(resource_base.ResourceBase):
|
||||
|
@ -59,6 +61,47 @@ class ResourceBaseTestCase(base.TestCase):
|
|||
self.base_resource.invalidate(force_refresh=True)
|
||||
self.conn.get.assert_called_once_with(path='/Foo')
|
||||
|
||||
def test_refresh_archive(self):
|
||||
mock_response = mock.Mock(
|
||||
headers={'content-type': 'application/zip'})
|
||||
with open('sushy/tests/unit/json_samples/TestRegistry.zip', 'rb') as f:
|
||||
mock_response.content = f.read()
|
||||
self.conn.get.return_value = mock_response
|
||||
|
||||
resource = BaseResource(connector=self.conn,
|
||||
path='/Foo',
|
||||
redfish_version='1.0.2',
|
||||
reader=resource_base.
|
||||
JsonArchiveReader('Test.2.0.json'))
|
||||
|
||||
self.assertIsNotNone(resource._json)
|
||||
self.assertEqual('Test.2.0.0', resource._json['Id'])
|
||||
|
||||
@mock.patch.object(resource_base, 'LOG', autospec=True)
|
||||
def test_refresh_archive_not_implemented(self, mock_log):
|
||||
mock_response = mock.Mock(
|
||||
headers={'content-type': 'application/gzip'})
|
||||
self.conn.get.return_value = mock_response
|
||||
BaseResource(connector=self.conn,
|
||||
path='/Foo',
|
||||
redfish_version='1.0.2',
|
||||
reader=resource_base.JsonArchiveReader('Test.2.0.json'))
|
||||
mock_log.error.assert_called_once()
|
||||
|
||||
@mock.patch.object(io, 'BytesIO', autospec=True)
|
||||
def test_refresh_archive_badzip_error(self, mock_io):
|
||||
mock_response = mock.Mock(
|
||||
headers={'content-type': 'application/zip'})
|
||||
mock_io.side_effect = zipfile.BadZipfile('Something wrong')
|
||||
self.conn.get.return_value = mock_response
|
||||
|
||||
self.assertRaises(exceptions.SushyError,
|
||||
BaseResource, connector=self.conn,
|
||||
path='/Foo',
|
||||
redfish_version='1.0.2',
|
||||
reader=resource_base.
|
||||
JsonArchiveReader('Test.2.0.json'))
|
||||
|
||||
|
||||
class TestResource(resource_base.ResourceBase):
|
||||
"""A concrete Test Resource to test against"""
|
||||
|
|
Loading…
Reference in New Issue