Move ab_streaming_metadata under ab_config.

Add new config: ab_config.verify_payload_metadata.

Change-Id: I521dff92166c33dd9c2efa498dc599fe4bc83fde
Signed-off-by: Zhomart Mukhamejanov <zhomart@google.com>
Test: mmm, junit
Bug: 77150191
This commit is contained in:
Zhomart Mukhamejanov 2018-08-21 12:19:02 -07:00
parent 0115b79cbd
commit 88712f7610
11 changed files with 117 additions and 105 deletions

View file

@ -220,7 +220,7 @@ privileged system app, so it's granted the required permissions to access
- [x] Add Sample app update state (separate from update_engine status) - [x] Add Sample app update state (separate from update_engine status)
- [x] Add smart update completion detection using onStatusUpdate - [x] Add smart update completion detection using onStatusUpdate
- [x] Add pause/resume demo - [x] Add pause/resume demo
- [ ] Verify system partition checksum for package - [x] Verify system partition checksum for package
## Running tests ## Running tests

View file

@ -25,6 +25,7 @@ import org.json.JSONObject;
import java.io.File; import java.io.File;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.Optional; import java.util.Optional;
/** /**
@ -67,31 +68,28 @@ public class UpdateConfig implements Parcelable {
throw new JSONException("Invalid type, expected either " throw new JSONException("Invalid type, expected either "
+ "NON_STREAMING or STREAMING, got " + o.getString("ab_install_type")); + "NON_STREAMING or STREAMING, got " + o.getString("ab_install_type"));
} }
if (c.mAbInstallType == AB_INSTALL_TYPE_STREAMING) {
JSONObject meta = o.getJSONObject("ab_streaming_metadata");
JSONArray propertyFilesJson = meta.getJSONArray("property_files");
PackageFile[] propertyFiles =
new PackageFile[propertyFilesJson.length()];
for (int i = 0; i < propertyFilesJson.length(); i++) {
JSONObject p = propertyFilesJson.getJSONObject(i);
propertyFiles[i] = new PackageFile(
p.getString("filename"),
p.getLong("offset"),
p.getLong("size"));
}
String authorization = null;
if (meta.has("authorization")) {
authorization = meta.getString("authorization");
}
c.mAbStreamingMetadata = new StreamingMetadata(
propertyFiles,
authorization);
}
// TODO: parse only for A/B updates when non-A/B is implemented // TODO: parse only for A/B updates when non-A/B is implemented
JSONObject ab = o.getJSONObject("ab_config"); JSONObject ab = o.getJSONObject("ab_config");
boolean forceSwitchSlot = ab.getBoolean("force_switch_slot"); boolean forceSwitchSlot = ab.getBoolean("force_switch_slot");
c.mAbConfig = new AbConfig(forceSwitchSlot); boolean verifyPayloadMetadata = ab.getBoolean("verify_payload_metadata");
ArrayList<PackageFile> propertyFiles = new ArrayList<>();
if (ab.has("property_files")) {
JSONArray propertyFilesJson = ab.getJSONArray("property_files");
for (int i = 0; i < propertyFilesJson.length(); i++) {
JSONObject p = propertyFilesJson.getJSONObject(i);
propertyFiles.add(new PackageFile(
p.getString("filename"),
p.getLong("offset"),
p.getLong("size")));
}
}
String authorization = ab.optString("authorization", null);
c.mAbConfig = new AbConfig(
forceSwitchSlot,
verifyPayloadMetadata,
propertyFiles.toArray(new PackageFile[0]),
authorization);
c.mRawJson = json; c.mRawJson = json;
return c; return c;
@ -112,9 +110,6 @@ public class UpdateConfig implements Parcelable {
/** non-streaming (first saves locally) OR streaming (on the fly) */ /** non-streaming (first saves locally) OR streaming (on the fly) */
private int mAbInstallType; private int mAbInstallType;
/** metadata is required only for streaming update */
private StreamingMetadata mAbStreamingMetadata;
/** A/B update configurations */ /** A/B update configurations */
private AbConfig mAbConfig; private AbConfig mAbConfig;
@ -127,7 +122,6 @@ public class UpdateConfig implements Parcelable {
this.mName = in.readString(); this.mName = in.readString();
this.mUrl = in.readString(); this.mUrl = in.readString();
this.mAbInstallType = in.readInt(); this.mAbInstallType = in.readInt();
this.mAbStreamingMetadata = (StreamingMetadata) in.readSerializable();
this.mAbConfig = (AbConfig) in.readSerializable(); this.mAbConfig = (AbConfig) in.readSerializable();
this.mRawJson = in.readString(); this.mRawJson = in.readString();
} }
@ -154,10 +148,6 @@ public class UpdateConfig implements Parcelable {
return mAbInstallType; return mAbInstallType;
} }
public StreamingMetadata getStreamingMetadata() {
return mAbStreamingMetadata;
}
public AbConfig getAbConfig() { public AbConfig getAbConfig() {
return mAbConfig; return mAbConfig;
} }
@ -185,42 +175,10 @@ public class UpdateConfig implements Parcelable {
dest.writeString(mName); dest.writeString(mName);
dest.writeString(mUrl); dest.writeString(mUrl);
dest.writeInt(mAbInstallType); dest.writeInt(mAbInstallType);
dest.writeSerializable(mAbStreamingMetadata);
dest.writeSerializable(mAbConfig); dest.writeSerializable(mAbConfig);
dest.writeString(mRawJson); dest.writeString(mRawJson);
} }
/**
* Metadata for streaming A/B update.
*/
public static class StreamingMetadata implements Serializable {
private static final long serialVersionUID = 31042L;
/** defines beginning of update data in archive */
private PackageFile[] mPropertyFiles;
/**
* SystemUpdaterSample receives the authorization token from the OTA server, in addition
* to the package URL. It passes on the info to update_engine, so that the latter can
* fetch the data from the package server directly with the token.
*/
private String mAuthorization;
public StreamingMetadata(PackageFile[] propertyFiles, String authorization) {
this.mPropertyFiles = propertyFiles;
this.mAuthorization = authorization;
}
public PackageFile[] getPropertyFiles() {
return mPropertyFiles;
}
public Optional<String> getAuthorization() {
return mAuthorization == null ? Optional.empty() : Optional.of(mAuthorization);
}
}
/** /**
* Description of a file in an OTA package zip file. * Description of a file in an OTA package zip file.
*/ */
@ -269,14 +227,48 @@ public class UpdateConfig implements Parcelable {
*/ */
private boolean mForceSwitchSlot; private boolean mForceSwitchSlot;
public AbConfig(boolean forceSwitchSlot) { /**
* if set true device will boot to new slot, otherwise user manually
* switches slot on the screen.
*/
private boolean mVerifyPayloadMetadata;
/** defines beginning of update data in archive */
private PackageFile[] mPropertyFiles;
/**
* SystemUpdaterSample receives the authorization token from the OTA server, in addition
* to the package URL. It passes on the info to update_engine, so that the latter can
* fetch the data from the package server directly with the token.
*/
private String mAuthorization;
public AbConfig(
boolean forceSwitchSlot,
boolean verifyPayloadMetadata,
PackageFile[] propertyFiles,
String authorization) {
this.mForceSwitchSlot = forceSwitchSlot; this.mForceSwitchSlot = forceSwitchSlot;
this.mVerifyPayloadMetadata = verifyPayloadMetadata;
this.mPropertyFiles = propertyFiles;
this.mAuthorization = authorization;
} }
public boolean getForceSwitchSlot() { public boolean getForceSwitchSlot() {
return mForceSwitchSlot; return mForceSwitchSlot;
} }
public boolean getVerifyPayloadMetadata() {
return mVerifyPayloadMetadata;
}
public PackageFile[] getPropertyFiles() {
return mPropertyFiles;
}
public Optional<String> getAuthorization() {
return mAuthorization == null ? Optional.empty() : Optional.of(mAuthorization);
}
} }
} }

View file

@ -324,7 +324,7 @@ public class UpdateManager {
if (code == PrepareStreamingService.RESULT_CODE_SUCCESS) { if (code == PrepareStreamingService.RESULT_CODE_SUCCESS) {
builder.setPayload(payloadSpec); builder.setPayload(payloadSpec);
builder.addExtraProperty("USER_AGENT=" + HTTP_USER_AGENT); builder.addExtraProperty("USER_AGENT=" + HTTP_USER_AGENT);
config.getStreamingMetadata() config.getAbConfig()
.getAuthorization() .getAuthorization()
.ifPresent(s -> builder.addExtraProperty("AUTHORIZATION=" + s)); .ifPresent(s -> builder.addExtraProperty("AUTHORIZATION=" + s));
updateEngineApplyPayload(builder.build()); updateEngineApplyPayload(builder.build());

View file

@ -173,7 +173,7 @@ public class PrepareStreamingService extends IntentService {
} }
/** /**
* Downloads files defined in {@link UpdateConfig#getStreamingMetadata()} * Downloads files defined in {@link UpdateConfig#getAbConfig()}
* and exists in {@code PRE_STREAMING_FILES_SET}, and put them * and exists in {@code PRE_STREAMING_FILES_SET}, and put them
* in directory {@code dir}. * in directory {@code dir}.
* @throws IOException when can't download a file * @throws IOException when can't download a file
@ -185,7 +185,7 @@ public class PrepareStreamingService extends IntentService {
Files.deleteIfExists(Paths.get(OTA_PACKAGE_DIR, file)); Files.deleteIfExists(Paths.get(OTA_PACKAGE_DIR, file));
} }
Log.d(TAG, "Downloading files to " + dir); Log.d(TAG, "Downloading files to " + dir);
for (UpdateConfig.PackageFile file : config.getStreamingMetadata().getPropertyFiles()) { for (UpdateConfig.PackageFile file : config.getAbConfig().getPropertyFiles()) {
if (PRE_STREAMING_FILES_SET.contains(file.getFilename())) { if (PRE_STREAMING_FILES_SET.contains(file.getFilename())) {
Log.d(TAG, "Downloading file " + file.getFilename()); Log.d(TAG, "Downloading file " + file.getFilename());
FileDownloader downloader = new FileDownloader( FileDownloader downloader = new FileDownloader(

View file

@ -83,7 +83,7 @@ public final class UpdateConfigs {
/** /**
* @param filename searches by given filename * @param filename searches by given filename
* @param config searches in {@link UpdateConfig#getStreamingMetadata()} * @param config searches in {@link UpdateConfig#getAbConfig()}
* @return offset and size of {@code filename} in the package zip file * @return offset and size of {@code filename} in the package zip file
* stored as {@link UpdateConfig.PackageFile}. * stored as {@link UpdateConfig.PackageFile}.
*/ */
@ -91,7 +91,7 @@ public final class UpdateConfigs {
final String filename, final String filename,
UpdateConfig config) { UpdateConfig config) {
return Arrays return Arrays
.stream(config.getStreamingMetadata().getPropertyFiles()) .stream(config.getAbConfig().getPropertyFiles())
.filter(file -> filename.equals(file.getFilename())) .filter(file -> filename.equals(file.getFilename()))
.findFirst(); .findFirst();
} }

View file

@ -2,7 +2,9 @@
"name": "streaming-001", "name": "streaming-001",
"url": "http://foo.bar/update.zip", "url": "http://foo.bar/update.zip",
"ab_install_type": "STREAMING", "ab_install_type": "STREAMING",
"ab_streaming_metadata": { "ab_config": {
"verify_payload_metadata": true,
"force_switch_slot": true,
"property_files": [ "property_files": [
{ {
"filename": "payload.bin", "filename": "payload.bin",
@ -10,8 +12,5 @@
"size": 8 "size": 8
} }
] ]
},
"ab_config": {
"force_switch_slot": true
} }
} }

View file

@ -1,10 +1,8 @@
{ {
"__": "*** Generated using tools/gen_update_config.py ***", "__": "*** Generated using tools/gen_update_config.py ***",
"ab_config": { "ab_config": {
"force_switch_slot": false "verify_payload_metadata": true,
}, "force_switch_slot": false,
"ab_install_type": "STREAMING",
"ab_streaming_metadata": {
"property_files": [ "property_files": [
{ {
"filename": "payload_metadata.bin", "filename": "payload_metadata.bin",
@ -38,6 +36,7 @@
} }
] ]
}, },
"ab_install_type": "STREAMING",
"name": "S ota_002_package", "name": "S ota_002_package",
"url": "file:///data/my-sample-ota-builds-dir/ota_002_package.zip" "url": "file:///data/my-sample-ota-builds-dir/ota_002_package.zip"
} }

View file

@ -1,7 +1,15 @@
{ {
"__": "*** Generated using tools/gen_update_config.py ***", "__": "*** Generated using tools/gen_update_config.py ***",
"ab_config": { "ab_config": {
"force_switch_slot": false "verify_payload_metadata": true,
"force_switch_slot": false,
"property_files": [
{
"filename": "payload.bin",
"offset": 195,
"size": 8
}
]
}, },
"ab_install_type": "NON_STREAMING", "ab_install_type": "NON_STREAMING",
"name": "S ota_002_package", "name": "S ota_002_package",

View file

@ -44,10 +44,12 @@ import java.io.InputStreamReader;
@SmallTest @SmallTest
public class UpdateConfigTest { public class UpdateConfigTest {
private static final String JSON_NON_STREAMING = private static final String JSON_NON_STREAMING = "{"
"{\"name\": \"vip update\", \"url\": \"file:///builds/a.zip\", " + " \"name\": \"vip update\", \"url\": \"file:///my-builds/a.zip\","
+ " \"ab_install_type\": \"NON_STREAMING\"," + " \"ab_install_type\": \"NON_STREAMING\","
+ " \"ab_config\": { \"force_switch_slot\": false } }"; + " \"ab_config\": {"
+ " \"force_switch_slot\": false,"
+ " \"verify_payload_metadata\": false } }";
@Rule @Rule
public final ExpectedException thrown = ExpectedException.none(); public final ExpectedException thrown = ExpectedException.none();
@ -71,7 +73,7 @@ public class UpdateConfigTest {
assertSame("type is parsed", assertSame("type is parsed",
UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING, UpdateConfig.AB_INSTALL_TYPE_NON_STREAMING,
config.getInstallType()); config.getInstallType());
assertEquals("url is parsed", "file:///builds/a.zip", config.getUrl()); assertEquals("url is parsed", "file:///my-builds/a.zip", config.getUrl());
} }
@Test @Test
@ -81,9 +83,9 @@ public class UpdateConfigTest {
assertEquals("http://foo.bar/update.zip", config.getUrl()); assertEquals("http://foo.bar/update.zip", config.getUrl());
assertSame(UpdateConfig.AB_INSTALL_TYPE_STREAMING, config.getInstallType()); assertSame(UpdateConfig.AB_INSTALL_TYPE_STREAMING, config.getInstallType());
assertEquals("payload.bin", assertEquals("payload.bin",
config.getStreamingMetadata().getPropertyFiles()[0].getFilename()); config.getAbConfig().getPropertyFiles()[0].getFilename());
assertEquals(195, config.getStreamingMetadata().getPropertyFiles()[0].getOffset()); assertEquals(195, config.getAbConfig().getPropertyFiles()[0].getOffset());
assertEquals(8, config.getStreamingMetadata().getPropertyFiles()[0].getSize()); assertEquals(8, config.getAbConfig().getPropertyFiles()[0].getSize());
assertTrue(config.getAbConfig().getForceSwitchSlot()); assertTrue(config.getAbConfig().getForceSwitchSlot());
} }
@ -96,9 +98,12 @@ public class UpdateConfigTest {
@Test @Test
public void getUpdatePackageFile_throwsErrorIfNotAFile() throws Exception { public void getUpdatePackageFile_throwsErrorIfNotAFile() throws Exception {
String json = "{\"name\": \"upd\", \"url\": \"http://foo.bar\"," String json = "{"
+ " \"name\": \"upd\", \"url\": \"http://foo.bar\","
+ " \"ab_install_type\": \"NON_STREAMING\"," + " \"ab_install_type\": \"NON_STREAMING\","
+ " \"ab_config\": { \"force_switch_slot\": false } }"; + " \"ab_config\": {"
+ " \"force_switch_slot\": false,"
+ " \"verify_payload_metadata\": false } }";
UpdateConfig config = UpdateConfig.fromJson(json); UpdateConfig config = UpdateConfig.fromJson(json);
thrown.expect(RuntimeException.class); thrown.expect(RuntimeException.class);
config.getUpdatePackageFile(); config.getUpdatePackageFile();
@ -107,7 +112,7 @@ public class UpdateConfigTest {
@Test @Test
public void getUpdatePackageFile_works() throws Exception { public void getUpdatePackageFile_works() throws Exception {
UpdateConfig c = UpdateConfig.fromJson(JSON_NON_STREAMING); UpdateConfig c = UpdateConfig.fromJson(JSON_NON_STREAMING);
assertEquals("/builds/a.zip", c.getUpdatePackageFile().getAbsolutePath()); assertEquals("/my-builds/a.zip", c.getUpdatePackageFile().getAbsolutePath());
} }
private String readResource(int id) throws IOException { private String readResource(int id) throws IOException {

View file

@ -46,11 +46,17 @@ class GenUpdateConfig(object):
AB_INSTALL_TYPE_STREAMING = 'STREAMING' AB_INSTALL_TYPE_STREAMING = 'STREAMING'
AB_INSTALL_TYPE_NON_STREAMING = 'NON_STREAMING' AB_INSTALL_TYPE_NON_STREAMING = 'NON_STREAMING'
def __init__(self, package, url, ab_install_type, ab_force_switch_slot): def __init__(self,
package,
url,
ab_install_type,
ab_force_switch_slot,
ab_verify_payload_metadata):
self.package = package self.package = package
self.url = url self.url = url
self.ab_install_type = ab_install_type self.ab_install_type = ab_install_type
self.ab_force_switch_slot = ab_force_switch_slot self.ab_force_switch_slot = ab_force_switch_slot
self.ab_verify_payload_metadata = ab_verify_payload_metadata
self.streaming_required = ( self.streaming_required = (
# payload.bin and payload_properties.txt must exist. # payload.bin and payload_properties.txt must exist.
'payload.bin', 'payload.bin',
@ -71,29 +77,24 @@ class GenUpdateConfig(object):
def run(self): def run(self):
"""Generates config.""" """Generates config."""
streaming_metadata = None
if self.ab_install_type == GenUpdateConfig.AB_INSTALL_TYPE_STREAMING:
streaming_metadata = self._gen_ab_streaming_metadata()
self._config = { self._config = {
'__': '*** Generated using tools/gen_update_config.py ***', '__': '*** Generated using tools/gen_update_config.py ***',
'name': self.ab_install_type[0] + ' ' + os.path.basename(self.package)[:-4], 'name': self.ab_install_type[0] + ' ' + os.path.basename(self.package)[:-4],
'url': self.url, 'url': self.url,
'ab_streaming_metadata': streaming_metadata, 'ab_config': self._gen_ab_config(),
'ab_install_type': self.ab_install_type, 'ab_install_type': self.ab_install_type,
'ab_config': { }
def _gen_ab_config(self):
"""Builds config required for A/B update."""
with zipfile.ZipFile(self.package, 'r') as package_zip:
config = {
'property_files': self._get_property_files(package_zip),
'verify_payload_metadata': self.ab_verify_payload_metadata,
'force_switch_slot': self.ab_force_switch_slot, 'force_switch_slot': self.ab_force_switch_slot,
} }
}
def _gen_ab_streaming_metadata(self): return config
"""Builds metadata for files required for streaming update."""
with zipfile.ZipFile(self.package, 'r') as package_zip:
metadata = {
'property_files': self._get_property_files(package_zip)
}
return metadata
@staticmethod @staticmethod
def _get_property_files(package_zip): def _get_property_files(package_zip):
@ -135,6 +136,11 @@ def main(): # pylint: disable=missing-docstring
action='store_true', action='store_true',
help='if set device will boot to a new slot, otherwise user ' help='if set device will boot to a new slot, otherwise user '
'manually switches slot on the screen') 'manually switches slot on the screen')
parser.add_argument('--ab_verify_payload_metadata',
default=False,
action='store_true',
help='if set the app will verify the update payload metadata using '
'update_engine before downloading the whole package.')
parser.add_argument('package', parser.add_argument('package',
type=str, type=str,
help='OTA package zip file') help='OTA package zip file')
@ -154,7 +160,8 @@ def main(): # pylint: disable=missing-docstring
package=args.package, package=args.package,
url=args.url, url=args.url,
ab_install_type=args.ab_install_type, ab_install_type=args.ab_install_type,
ab_force_switch_slot=args.ab_force_switch_slot) ab_force_switch_slot=args.ab_force_switch_slot,
ab_verify_payload_metadata=args.ab_verify_payload_metadata)
gen.run() gen.run()
gen.write(args.out) gen.write(args.out)
print('Config is written to ' + args.out) print('Config is written to ' + args.out)

View file

@ -32,7 +32,7 @@ class GenUpdateConfigTest(unittest.TestCase): # pylint: disable=missing-docstrin
def test_ab_install_type_streaming(self): def test_ab_install_type_streaming(self):
"""tests if streaming property files' offset and size are generated properly""" """tests if streaming property files' offset and size are generated properly"""
config, package = self._generate_config() config, package = self._generate_config()
property_files = config['ab_streaming_metadata']['property_files'] property_files = config['ab_config']['property_files']
self.assertEqual(len(property_files), 6) self.assertEqual(len(property_files), 6)
with open(package, 'rb') as pkg_file: with open(package, 'rb') as pkg_file:
for prop in property_files: for prop in property_files:
@ -56,6 +56,8 @@ class GenUpdateConfigTest(unittest.TestCase): # pylint: disable=missing-docstrin
'../tests/res/raw/ota_002_package.zip') '../tests/res/raw/ota_002_package.zip')
gen = GenUpdateConfig(ota_package, gen = GenUpdateConfig(ota_package,
'file:///foo.bar', 'file:///foo.bar',
GenUpdateConfig.AB_INSTALL_TYPE_STREAMING) GenUpdateConfig.AB_INSTALL_TYPE_STREAMING,
True, # ab_force_switch_slot
True) # ab_verify_payload_metadata
gen.run() gen.run()
return gen.config, ota_package return gen.config, ota_package