Merge "Add support for loading resources from archive file"

This commit is contained in:
Zuul 2018-11-30 17:02:51 +00:00 committed by Gerrit Code Review
commit 3bf894c097
4 changed files with 115 additions and 3 deletions

View File

@ -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"""

View File

@ -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.

View File

@ -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"""