Allow xbuddy to accept any remote urls (including trybot urls).
This changes xbuddy to attempt to lookup a remote board by using the remote
path as is before trying it with the commonly used + -release suffix.
This also makes xbuddy fail faster if someone tries to use a remote url that
doesn't exist since the exception is raised during url resolution rather than
when we are trying to use the artifact.
BUG=chromium:306609
TEST=image_to_live with --image=xbuddy:remote/trybot-test-ap/R32-4777.0.0-b0
+ unit tests.
Change-Id: I352cd93ca2af2b048353dc349b08f734cf8095e7
Reviewed-on: https://chromium-review.googlesource.com/172764
Reviewed-by: Chris Sosa <sosa@chromium.org>
Commit-Queue: Chris Sosa <sosa@chromium.org>
Tested-by: Chris Sosa <sosa@chromium.org>
diff --git a/devserver_constants.py b/devserver_constants.py
index f8f18c6..c0ac645 100644
--- a/devserver_constants.py
+++ b/devserver_constants.py
@@ -8,8 +8,8 @@
# TODO (joyc) move the google storage filenames of artfacts here
CHANNELS = 'canary', 'dev', 'beta', 'stable'
GS_IMAGE_DIR = 'gs://chromeos-image-archive'
-GS_LATEST_MASTER = GS_IMAGE_DIR + '/%(board)s-%(suffix)s/LATEST-master'
-IMAGE_DIR = '%(board)s-%(suffix)s/%(version)s'
+GS_LATEST_MASTER = GS_IMAGE_DIR + '/%(board)s%(suffix)s/LATEST-master'
+IMAGE_DIR = '%(board)s%(suffix)s/%(version)s'
GS_RELEASES_DIR = 'gs://chromeos-releases'
GS_CHANNEL_DIR = GS_RELEASES_DIR + '/%(channel)s-channel/%(board)s/'
diff --git a/xbuddy.py b/xbuddy.py
index 5fff21a..7b5c857 100644
--- a/xbuddy.py
+++ b/xbuddy.py
@@ -106,7 +106,7 @@
LATEST_OFFICIAL = "latest-official"
-RELEASE = "release"
+RELEASE = "-release"
class XBuddyException(Exception):
@@ -288,7 +288,6 @@
return devserver_constants.IMAGE_DIR % {'board':board,
'suffix':suffix,
'version':version}
-
def _LookupChannel(self, board, channel='stable'):
"""Check the channel folder for the version number of interest."""
# Get all names in channel dir. Get 10 highest directories by version.
@@ -327,7 +326,33 @@
'suffix':RELEASE,
'version':full_version}
- def _ResolveVersionToUrl(self, board, version):
+ def _RemoteBuildId(self, board, version):
+ """Returns the remote build_id for the given board and version.
+
+ Raises:
+ XBuddyException: If we failed to resolve the version to a valid build_id.
+ """
+ build_id_as_is = devserver_constants.IMAGE_DIR % {'board':board,
+ 'suffix':'',
+ 'version':version}
+ build_id_release = devserver_constants.IMAGE_DIR % {'board':board,
+ 'suffix':RELEASE,
+ 'version':version}
+ # Return the first path that exists. We assume that what the user typed
+ # is better than with a default suffix added i.e. x86-generic/blah is
+ # more valuable than x86-generic-release/blah.
+ for build_id in build_id_as_is, build_id_release:
+ cmd = 'gsutil ls %s/%s' % (devserver_constants.GS_IMAGE_DIR, build_id)
+ try:
+ version = gsutil_util.GSUtilRun(cmd, None)
+ return build_id
+ except gsutil_util.GSUtilError:
+ continue
+ else:
+ raise XBuddyException('Could not find remote build_id for %s %s' % (
+ board, version))
+
+ def _ResolveVersionToBuildId(self, board, version):
"""Handle version aliases for remote payloads in GS.
Args:
@@ -340,19 +365,16 @@
4. version prefix (i.e. RX-Y.X, RX-Y, RX)
Returns:
- Location where the image dir is actually found on GS
+ Location where the image dir is actually found on GS (build_id)
+ Raises:
+ XBuddyException: If we failed to resolve the version to a valid url.
"""
- # TODO(joychen): Convert separate calls to a dict + error out bad paths.
-
# Only the last segment of the alias is variable relative to the rest.
version_tuple = version.rsplit('-', 1)
if re.match(devserver_constants.VERSION_RE, version):
- # This is supposed to be a complete version number on GS. Return it.
- return devserver_constants.IMAGE_DIR % {'board':board,
- 'suffix':RELEASE,
- 'version':version}
+ return self._RemoteBuildId(board, version)
elif version == LATEST_OFFICIAL:
# latest-official --> LATEST build in board-release
return self._LookupOfficial(board)
@@ -628,7 +650,7 @@
if image_type not in GS_ALIASES:
raise XBuddyException('Bad remote image type: %s. Use one of: %s' %
(image_type, GS_ALIASES))
- build_id = self._ResolveVersionToUrl(board, version)
+ build_id = self._ResolveVersionToBuildId(board, version)
_Log('Resolved version %s to %s.', version, build_id)
file_name = GS_ALIAS_TO_FILENAME[image_type]
if not lookup_only:
diff --git a/xbuddy_unittest.py b/xbuddy_unittest.py
index a509541..9bd2a31 100755
--- a/xbuddy_unittest.py
+++ b/xbuddy_unittest.py
@@ -54,7 +54,7 @@
mox.IgnoreArg()).AndReturn('v')
expected = 'b-s/v'
self.mox.ReplayAll()
- self.assertEqual(self.mock_xb._LookupOfficial('b', 's'), expected)
+ self.assertEqual(self.mock_xb._LookupOfficial('b', '-s'), expected)
self.mox.VerifyAll()
def testLookupChannel(self):
@@ -70,24 +70,25 @@
self.assertEqual(self.mock_xb._LookupChannel('b'), expected)
self.mox.VerifyAll()
- def testResolveVersionToUrl_Official(self):
- """Check _ResolveVersionToUrl recognizes aliases for official builds."""
+ def testResolveVersionToBuildId_Official(self):
+ """Check _ResolveVersionToBuildId recognizes aliases for official builds."""
board = 'b'
# aliases that should be redirected to LookupOfficial
+
self.mox.StubOutWithMock(self.mock_xb, '_LookupOfficial')
self.mock_xb._LookupOfficial(board)
self.mock_xb._LookupOfficial(board, 'paladin')
self.mox.ReplayAll()
version = 'latest-official'
- self.mock_xb._ResolveVersionToUrl(board, version)
+ self.mock_xb._ResolveVersionToBuildId(board, version)
version = 'latest-official-paladin'
- self.mock_xb._ResolveVersionToUrl(board, version)
+ self.mock_xb._ResolveVersionToBuildId(board, version)
self.mox.VerifyAll()
- def testResolveVersionToUrl_Channel(self):
- """Check _ResolveVersionToUrl recognizes aliases for channels."""
+ def testResolveVersionToBuildId_Channel(self):
+ """Check _ResolveVersionToBuildId recognizes aliases for channels."""
board = 'b'
# aliases that should be redirected to LookupChannel
@@ -97,9 +98,9 @@
self.mox.ReplayAll()
version = 'latest'
- self.mock_xb._ResolveVersionToUrl(board, version)
+ self.mock_xb._ResolveVersionToBuildId(board, version)
version = 'latest-dev'
- self.mock_xb._ResolveVersionToUrl(board, version)
+ self.mock_xb._ResolveVersionToBuildId(board, version)
self.mox.VerifyAll()
def testBasicInterpretPath(self):
@@ -184,11 +185,18 @@
"""Caching & replacement of timestamp files."""
path_a = ('remote', 'a', 'R0', 'test')
path_b = ('remote', 'b', 'R0', 'test')
-
+ self.mox.StubOutWithMock(gsutil_util, 'GSUtilRun')
+ self.mox.StubOutWithMock(self.mock_xb, '_Download')
self.mox.StubOutWithMock(self.mock_xb, '_Download')
for _ in range(8):
self.mock_xb._Download(mox.IsA(str), mox.In(mox.IsA(str)))
+ # All non-release urls are invalid so we can meet expectations.
+ gsutil_util.GSUtilRun(mox.Not(mox.StrContains('-release')),
+ None).MultipleTimes().AndRaise(
+ gsutil_util.GSUtilError('bad url'))
+ gsutil_util.GSUtilRun(mox.StrContains('-release'), None).MultipleTimes()
+
self.mox.ReplayAll()
# requires default capacity