releasetools: Create PropertyFiles class.

And move StreamingPropertyFiles as its subclass. We will need similar
PropertyFiles instance for non-A/B OTA as well (to expose the
offset/size for the METADATA entry).

Bug: 74210298
Test: python -m unittest test_ota_from_target_files
Test: Generate an A/B OTA. Check the generated property-files string.
Test: pylint --rcfile=pylintrc \
          ota_from_target_files.py \
          test_ota_from_target_files.py
Change-Id: If90d97f0b330749fd8a6cde2ed9d0d6cd6ea60a8
This commit is contained in:
Tao Bao 2018-03-08 16:09:01 -08:00
parent 34af6a41ff
commit 69203525e4
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)