Merge "releasetools: Create PropertyFiles class."
am: febe7b0736
Change-Id: I2c8ead845861ec8549a76c54be74a7af12fe5dca
This commit is contained in:
commit
624f900c01
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue