From 1de89030a7218279a5dd225069fee1556abdcc7c Mon Sep 17 00:00:00 2001 From: Debayan Ray Date: Mon, 8 Feb 2016 06:36:32 -0800 Subject: [PATCH] Augmenting the hashing strategy It was only 'sha1' algorithm being used till now in ``hash_file`` method to create hash of the file contents. Need to add 'md5' to keep the consistency with that of IPA code base. Also, this method now supports all the algorithms hashlib does. As this method wasn't being used at all, therefore making the default algorithm to 'md5'. Will be used in oob firmware update (manual clean step) to verify the checksum of the images. Change-Id: I8159fd64d1c074bd539817a40900e34f8fae30d4 --- ironic/common/utils.py | 33 +++++++++++-- ironic/tests/unit/common/test_utils.py | 66 ++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 8 deletions(-) diff --git a/ironic/common/utils.py b/ironic/common/utils.py index 7566d65962..9652a1b08b 100644 --- a/ironic/common/utils.py +++ b/ironic/common/utils.py @@ -390,9 +390,36 @@ def file_open(*args, **kwargs): return file(*args, **kwargs) -def hash_file(file_like_object): - """Generate a hash for the contents of a file.""" - checksum = hashlib.sha1() +def _get_hash_object(hash_algo_name): + """Create a hash object based on given algorithm. + + :param hash_algo_name: name of the hashing algorithm. + :raises: InvalidParameterValue, on unsupported or invalid input. + :returns: a hash object based on the given named algorithm. + """ + algorithms = (hashlib.algorithms_guaranteed if six.PY3 + else hashlib.algorithms) + if hash_algo_name not in algorithms: + msg = (_("Unsupported/Invalid hash name '%s' provided.") + % hash_algo_name) + LOG.error(msg) + raise exception.InvalidParameterValue(msg) + + return getattr(hashlib, hash_algo_name)() + + +def hash_file(file_like_object, hash_algo='md5'): + """Generate a hash for the contents of a file. + + It returns a hash of the file object as a string of double length, + containing only hexadecimal digits. It supports all the algorithms + hashlib does. + :param file_like_object: file like object whose hash to be calculated. + :param hash_algo: name of the hashing strategy, default being 'md5'. + :raises: InvalidParameterValue, on unsupported or invalid input. + :returns: a condensed digest of the bytes of contents. + """ + checksum = _get_hash_object(hash_algo) for chunk in iter(lambda: file_like_object.read(32768), b''): checksum.update(chunk) return checksum.hexdigest() diff --git a/ironic/tests/unit/common/test_utils.py b/ironic/tests/unit/common/test_utils.py index 899b6889cf..9296632749 100644 --- a/ironic/tests/unit/common/test_utils.py +++ b/ironic/tests/unit/common/test_utils.py @@ -261,12 +261,68 @@ class GenericUtilsTestCase(base.TestCase): mock.ANY) fake_context_manager.__enter__.assert_called_once_with() - def test_hash_file(self): + @mock.patch.object(utils, 'hashlib', autospec=True) + def test__get_hash_object(self, hashlib_mock): + algorithms_available = ('md5', 'sha1', 'sha224', + 'sha256', 'sha384', 'sha512') + hashlib_mock.algorithms_guaranteed = algorithms_available + hashlib_mock.algorithms = algorithms_available + # | WHEN | + utils._get_hash_object('md5') + utils._get_hash_object('sha1') + utils._get_hash_object('sha224') + utils._get_hash_object('sha256') + utils._get_hash_object('sha384') + utils._get_hash_object('sha512') + # | THEN | + calls = [mock.call.md5(), mock.call.sha1(), mock.call.sha224(), + mock.call.sha256(), mock.call.sha384(), mock.call.sha512()] + hashlib_mock.assert_has_calls(calls) + + def test__get_hash_object_throws_for_invalid_or_unsupported_hash_name( + self): + # | WHEN | & | THEN | + self.assertRaises(exception.InvalidParameterValue, + utils._get_hash_object, + 'hickory-dickory-dock') + + def test_hash_file_for_md5(self): + # | GIVEN | data = b'Mary had a little lamb, its fleece as white as snow' - flo = six.BytesIO(data) - h1 = utils.hash_file(flo) - h2 = hashlib.sha1(data).hexdigest() - self.assertEqual(h1, h2) + file_like_object = six.BytesIO(data) + expected = hashlib.md5(data).hexdigest() + # | WHEN | + actual = utils.hash_file(file_like_object) # using default, 'md5' + # | THEN | + self.assertEqual(expected, actual) + + def test_hash_file_for_sha1(self): + # | GIVEN | + data = b'Mary had a little lamb, its fleece as white as snow' + file_like_object = six.BytesIO(data) + expected = hashlib.sha1(data).hexdigest() + # | WHEN | + actual = utils.hash_file(file_like_object, 'sha1') + # | THEN | + self.assertEqual(expected, actual) + + def test_hash_file_for_sha512(self): + # | GIVEN | + data = b'Mary had a little lamb, its fleece as white as snow' + file_like_object = six.BytesIO(data) + expected = hashlib.sha512(data).hexdigest() + # | WHEN | + actual = utils.hash_file(file_like_object, 'sha512') + # | THEN | + self.assertEqual(expected, actual) + + def test_hash_file_throws_for_invalid_or_unsupported_hash(self): + # | GIVEN | + data = b'Mary had a little lamb, its fleece as white as snow' + file_like_object = six.BytesIO(data) + # | WHEN | & | THEN | + self.assertRaises(exception.InvalidParameterValue, utils.hash_file, + file_like_object, 'hickory-dickory-dock') def test_is_valid_boolstr(self): self.assertTrue(utils.is_valid_boolstr('true'))