Merge "releasetools: Create PropertyFiles class."

am: febe7b0736

Change-Id: I2c8ead845861ec8549a76c54be74a7af12fe5dca
This commit is contained in:
Tao Bao 2018-03-15 22:50:43 +00:00 committed by android-build-merger
commit 624f900c01
2 changed files with 184 additions and 51 deletions

View File

@ -955,8 +955,15 @@ def GetPackageMetadata(target_info, source_info=None):
return metadata return metadata
class StreamingPropertyFiles(object): class PropertyFiles(object):
"""Computes the ota-streaming-property-files string for streaming A/B OTA. """A class that computes the property-files string for an OTA package.
A property-files string is a comma-separated string that contains the
offset/size info for an OTA package. The entries, which must be ZIP_STORED,
can be fetched directly with the package URL along with the offset/size info.
These strings can be used for streaming A/B OTAs, or allowing an updater to
download package metadata entry directly, without paying the cost of
downloading entire package.
Computing the final property-files string requires two passes. Because doing Computing the final property-files string requires two passes. Because doing
the whole package signing (with signapk.jar) will possibly reorder the ZIP the whole package signing (with signapk.jar) will possibly reorder the ZIP
@ -966,7 +973,7 @@ class StreamingPropertyFiles(object):
This class provides functions to be called for each pass. The general flow is This class provides functions to be called for each pass. The general flow is
as follows. as follows.
property_files = StreamingPropertyFiles() property_files = PropertyFiles()
# The first pass, which writes placeholders before doing initial signing. # The first pass, which writes placeholders before doing initial signing.
property_files.Compute() property_files.Compute()
SignOutput() SignOutput()
@ -981,17 +988,9 @@ class StreamingPropertyFiles(object):
""" """
def __init__(self): def __init__(self):
self.required = ( self.name = None
# payload.bin and payload_properties.txt must exist. self.required = ()
'payload.bin', self.optional = ()
'payload_properties.txt',
)
self.optional = (
# care_map.txt is available only if dm-verity is enabled.
'care_map.txt',
# compatibility.zip is available only if target supports Treble.
'compatibility.zip',
)
def Compute(self, input_zip): def Compute(self, input_zip):
"""Computes and returns a property-files string with placeholders. """Computes and returns a property-files string with placeholders.
@ -1083,7 +1082,26 @@ class StreamingPropertyFiles(object):
return ','.join(tokens) return ','.join(tokens)
def FinalizeMetadata(metadata, input_file, output_file): class StreamingPropertyFiles(PropertyFiles):
"""A subclass for computing the property-files for streaming A/B OTAs."""
def __init__(self):
super(StreamingPropertyFiles, self).__init__()
self.name = 'ota-streaming-property-files'
self.required = (
# payload.bin and payload_properties.txt must exist.
'payload.bin',
'payload_properties.txt',
)
self.optional = (
# care_map.txt is available only if dm-verity is enabled.
'care_map.txt',
# compatibility.zip is available only if target supports Treble.
'compatibility.zip',
)
def FinalizeMetadata(metadata, input_file, output_file, needed_property_files):
"""Finalizes the metadata and signs an A/B OTA package. """Finalizes the metadata and signs an A/B OTA package.
In order to stream an A/B OTA package, we need 'ota-streaming-property-files' In order to stream an A/B OTA package, we need 'ota-streaming-property-files'
@ -1101,14 +1119,14 @@ def FinalizeMetadata(metadata, input_file, output_file):
input_file: The input ZIP filename that doesn't contain the package METADATA input_file: The input ZIP filename that doesn't contain the package METADATA
entry yet. entry yet.
output_file: The final output ZIP filename. output_file: The final output ZIP filename.
needed_property_files: The list of PropertyFiles' to be generated.
""" """
output_zip = zipfile.ZipFile( output_zip = zipfile.ZipFile(
input_file, 'a', compression=zipfile.ZIP_DEFLATED) input_file, 'a', compression=zipfile.ZIP_DEFLATED)
property_files = StreamingPropertyFiles()
# Write the current metadata entry with placeholders. # Write the current metadata entry with placeholders.
metadata['ota-streaming-property-files'] = property_files.Compute(output_zip) for property_files in needed_property_files:
metadata[property_files.name] = property_files.Compute(output_zip)
WriteMetadata(metadata, output_zip) WriteMetadata(metadata, output_zip)
common.ZipClose(output_zip) common.ZipClose(output_zip)
@ -1122,14 +1140,14 @@ def FinalizeMetadata(metadata, input_file, output_file):
# Open the signed zip. Compute the final metadata that's needed for streaming. # Open the signed zip. Compute the final metadata that's needed for streaming.
with zipfile.ZipFile(prelim_signing, 'r') as prelim_signing_zip: with zipfile.ZipFile(prelim_signing, 'r') as prelim_signing_zip:
expected_length = len(metadata['ota-streaming-property-files']) for property_files in needed_property_files:
metadata['ota-streaming-property-files'] = property_files.Finalize( metadata[property_files.name] = property_files.Finalize(
prelim_signing_zip, expected_length) prelim_signing_zip, len(metadata[property_files.name]))
# Replace the METADATA entry. # Replace the METADATA entry.
common.ZipDelete(prelim_signing, METADATA_NAME) common.ZipDelete(prelim_signing, METADATA_NAME)
output_zip = zipfile.ZipFile(prelim_signing, 'a', output_zip = zipfile.ZipFile(
compression=zipfile.ZIP_DEFLATED) prelim_signing, 'a', compression=zipfile.ZIP_DEFLATED)
WriteMetadata(metadata, output_zip) WriteMetadata(metadata, output_zip)
common.ZipClose(output_zip) common.ZipClose(output_zip)
@ -1138,8 +1156,8 @@ def FinalizeMetadata(metadata, input_file, output_file):
# Reopen the final signed zip to double check the streaming metadata. # Reopen the final signed zip to double check the streaming metadata.
with zipfile.ZipFile(output_file, 'r') as output_zip: with zipfile.ZipFile(output_file, 'r') as output_zip:
property_files.Verify( for property_files in needed_property_files:
output_zip, metadata['ota-streaming-property-files'].strip()) property_files.Verify(output_zip, metadata[property_files.name].strip())
def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip): def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip):
@ -1555,7 +1573,10 @@ def WriteABOTAPackageWithBrilloScript(target_file, output_file,
# FinalizeMetadata(). # FinalizeMetadata().
common.ZipClose(output_zip) common.ZipClose(output_zip)
FinalizeMetadata(metadata, staging_file, output_file) needed_property_files = (
StreamingPropertyFiles(),
)
FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
def main(argv): def main(argv):

View File

@ -26,8 +26,8 @@ from ota_from_target_files import (
_LoadOemDicts, BuildInfo, GetPackageMetadata, _LoadOemDicts, BuildInfo, GetPackageMetadata,
GetTargetFilesZipForSecondaryImages, GetTargetFilesZipForSecondaryImages,
GetTargetFilesZipWithoutPostinstallConfig, GetTargetFilesZipWithoutPostinstallConfig,
Payload, PayloadSigner, POSTINSTALL_CONFIG, StreamingPropertyFiles, Payload, PayloadSigner, POSTINSTALL_CONFIG, PropertyFiles,
WriteFingerprintAssertion) StreamingPropertyFiles, WriteFingerprintAssertion)
def construct_target_files(secondary=False): def construct_target_files(secondary=False):
@ -590,7 +590,23 @@ class OtaFromTargetFilesTest(unittest.TestCase):
self.assertNotIn(POSTINSTALL_CONFIG, verify_zip.namelist()) self.assertNotIn(POSTINSTALL_CONFIG, verify_zip.namelist())
class StreamingPropertyFilesTest(unittest.TestCase): class TestPropertyFiles(PropertyFiles):
"""A class that extends PropertyFiles for testing purpose."""
def __init__(self):
super(TestPropertyFiles, self).__init__()
self.name = 'ota-test-property-files'
self.required = (
'required-entry1',
'required-entry2',
)
self.optional = (
'optional-entry1',
'optional-entry2',
)
class PropertyFilesTest(unittest.TestCase):
def tearDown(self): def tearDown(self):
common.Cleanup() common.Cleanup()
@ -607,7 +623,7 @@ class StreamingPropertyFilesTest(unittest.TestCase):
return zip_file return zip_file
@staticmethod @staticmethod
def _parse_streaming_metadata_string(data): def _parse_property_files_string(data):
result = {} result = {}
for token in data.split(','): for token in data.split(','):
name, info = token.split(':', 1) name, info = token.split(':', 1)
@ -627,47 +643,57 @@ class StreamingPropertyFilesTest(unittest.TestCase):
def test_Compute(self): def test_Compute(self):
entries = ( entries = (
'payload.bin', 'required-entry1',
'payload_properties.txt', 'required-entry2',
) )
zip_file = self._construct_zip_package(entries) zip_file = self._construct_zip_package(entries)
property_files = StreamingPropertyFiles() property_files = TestPropertyFiles()
with zipfile.ZipFile(zip_file, 'r') as zip_fp: with zipfile.ZipFile(zip_file, 'r') as zip_fp:
streaming_metadata = property_files.Compute(zip_fp) property_files_string = property_files.Compute(zip_fp)
tokens = self._parse_streaming_metadata_string(streaming_metadata) tokens = self._parse_property_files_string(property_files_string)
self.assertEqual(3, len(tokens)) self.assertEqual(3, len(tokens))
self._verify_entries(zip_file, tokens, entries) self._verify_entries(zip_file, tokens, entries)
def test_Compute_withCareMapTxtAndCompatibilityZip(self): def test_Compute_withOptionalEntries(self):
entries = ( entries = (
'payload.bin', 'required-entry1',
'payload_properties.txt', 'required-entry2',
'care_map.txt', 'optional-entry1',
'compatibility.zip', 'optional-entry2',
) )
zip_file = self._construct_zip_package(entries) zip_file = self._construct_zip_package(entries)
property_files = StreamingPropertyFiles() property_files = TestPropertyFiles()
with zipfile.ZipFile(zip_file, 'r') as zip_fp: with zipfile.ZipFile(zip_file, 'r') as zip_fp:
streaming_metadata = property_files.Compute(zip_fp) property_files_string = property_files.Compute(zip_fp)
tokens = self._parse_streaming_metadata_string(streaming_metadata) tokens = self._parse_property_files_string(property_files_string)
self.assertEqual(5, len(tokens)) self.assertEqual(5, len(tokens))
self._verify_entries(zip_file, tokens, entries) self._verify_entries(zip_file, tokens, entries)
def test_Compute_missingRequiredEntry(self):
entries = (
'required-entry2',
)
zip_file = self._construct_zip_package(entries)
property_files = TestPropertyFiles()
with zipfile.ZipFile(zip_file, 'r') as zip_fp:
self.assertRaises(KeyError, property_files.Compute, zip_fp)
def test_Finalize(self): def test_Finalize(self):
entries = [ entries = [
'payload.bin', 'required-entry1',
'payload_properties.txt', 'required-entry2',
'META-INF/com/android/metadata', 'META-INF/com/android/metadata',
] ]
zip_file = self._construct_zip_package(entries) zip_file = self._construct_zip_package(entries)
property_files = StreamingPropertyFiles() property_files = TestPropertyFiles()
with zipfile.ZipFile(zip_file, 'r') as zip_fp: with zipfile.ZipFile(zip_file, 'r') as zip_fp:
# pylint: disable=protected-access
raw_metadata = property_files._GetPropertyFilesString( raw_metadata = property_files._GetPropertyFilesString(
zip_fp, reserve_space=False) zip_fp, reserve_space=False)
streaming_metadata = property_files.Finalize(zip_fp, len(raw_metadata)) streaming_metadata = property_files.Finalize(zip_fp, len(raw_metadata))
tokens = self._parse_streaming_metadata_string(streaming_metadata) tokens = self._parse_property_files_string(streaming_metadata)
self.assertEqual(3, len(tokens)) self.assertEqual(3, len(tokens))
# 'META-INF/com/android/metadata' will be key'd as 'metadata' in the # 'META-INF/com/android/metadata' will be key'd as 'metadata' in the
@ -677,15 +703,17 @@ class StreamingPropertyFilesTest(unittest.TestCase):
def test_Finalize_assertReservedLength(self): def test_Finalize_assertReservedLength(self):
entries = ( entries = (
'payload.bin', 'required-entry1',
'payload_properties.txt', 'required-entry2',
'care_map.txt', 'optional-entry1',
'optional-entry2',
'META-INF/com/android/metadata', 'META-INF/com/android/metadata',
) )
zip_file = self._construct_zip_package(entries) zip_file = self._construct_zip_package(entries)
property_files = StreamingPropertyFiles() property_files = TestPropertyFiles()
with zipfile.ZipFile(zip_file, 'r') as zip_fp: with zipfile.ZipFile(zip_file, 'r') as zip_fp:
# First get the raw metadata string (i.e. without padding space). # First get the raw metadata string (i.e. without padding space).
# pylint: disable=protected-access
raw_metadata = property_files._GetPropertyFilesString( raw_metadata = property_files._GetPropertyFilesString(
zip_fp, reserve_space=False) zip_fp, reserve_space=False)
raw_length = len(raw_metadata) raw_length = len(raw_metadata)
@ -709,16 +737,100 @@ class StreamingPropertyFilesTest(unittest.TestCase):
self.assertEqual(' ' * 20, streaming_metadata[raw_length:]) self.assertEqual(' ' * 20, streaming_metadata[raw_length:])
def test_Verify(self): def test_Verify(self):
entries = (
'required-entry1',
'required-entry2',
'optional-entry1',
'optional-entry2',
'META-INF/com/android/metadata',
)
zip_file = self._construct_zip_package(entries)
property_files = TestPropertyFiles()
with zipfile.ZipFile(zip_file, 'r') as zip_fp:
# First get the raw metadata string (i.e. without padding space).
# pylint: disable=protected-access
raw_metadata = property_files._GetPropertyFilesString(
zip_fp, reserve_space=False)
# Should pass the test if verification passes.
property_files.Verify(zip_fp, raw_metadata)
# Or raise on verification failure.
self.assertRaises(
AssertionError, property_files.Verify, zip_fp, raw_metadata + 'x')
class StreamingPropertyFilesTest(PropertyFilesTest):
"""Additional sanity checks specialized for StreamingPropertyFiles."""
def test_init(self):
property_files = StreamingPropertyFiles()
self.assertEqual('ota-streaming-property-files', property_files.name)
self.assertEqual(
(
'payload.bin',
'payload_properties.txt',
),
property_files.required)
self.assertEqual(
(
'care_map.txt',
'compatibility.zip',
),
property_files.optional)
def test_Compute(self):
entries = ( entries = (
'payload.bin', 'payload.bin',
'payload_properties.txt', 'payload_properties.txt',
'care_map.txt', 'care_map.txt',
'compatibility.zip',
)
zip_file = self._construct_zip_package(entries)
property_files = StreamingPropertyFiles()
with zipfile.ZipFile(zip_file, 'r') as zip_fp:
property_files_string = property_files.Compute(zip_fp)
tokens = self._parse_property_files_string(property_files_string)
self.assertEqual(5, len(tokens))
self._verify_entries(zip_file, tokens, entries)
def test_Finalize(self):
entries = [
'payload.bin',
'payload_properties.txt',
'care_map.txt',
'compatibility.zip',
'META-INF/com/android/metadata',
]
zip_file = self._construct_zip_package(entries)
property_files = StreamingPropertyFiles()
with zipfile.ZipFile(zip_file, 'r') as zip_fp:
# pylint: disable=protected-access
raw_metadata = property_files._GetPropertyFilesString(
zip_fp, reserve_space=False)
streaming_metadata = property_files.Finalize(zip_fp, len(raw_metadata))
tokens = self._parse_property_files_string(streaming_metadata)
self.assertEqual(5, len(tokens))
# 'META-INF/com/android/metadata' will be key'd as 'metadata' in the
# streaming metadata.
entries[4] = 'metadata'
self._verify_entries(zip_file, tokens, entries)
def test_Verify(self):
entries = (
'payload.bin',
'payload_properties.txt',
'care_map.txt',
'compatibility.zip',
'META-INF/com/android/metadata', 'META-INF/com/android/metadata',
) )
zip_file = self._construct_zip_package(entries) zip_file = self._construct_zip_package(entries)
property_files = StreamingPropertyFiles() property_files = StreamingPropertyFiles()
with zipfile.ZipFile(zip_file, 'r') as zip_fp: with zipfile.ZipFile(zip_file, 'r') as zip_fp:
# First get the raw metadata string (i.e. without padding space). # First get the raw metadata string (i.e. without padding space).
# pylint: disable=protected-access
raw_metadata = property_files._GetPropertyFilesString( raw_metadata = property_files._GetPropertyFilesString(
zip_fp, reserve_space=False) zip_fp, reserve_space=False)