Devserver: Allow xbuddy translate to take in an image_dir kwarg.
Currently xbuddy is tied to gs://chromeos-image-archive. This change
allows us to specify the image directory in Google Storage to use
when requesting a translation.
BUG=chromium:359472
TEST=Able to find the latest build in other buckets.
TEST=xbuddy_unittests.
Change-Id: Iddc2ab2b0675591b53311ebe9e1692d0c0859377
Reviewed-on: https://chromium-review.googlesource.com/200683
Reviewed-by: Simran Basi <sbasi@chromium.org>
Tested-by: Simran Basi <sbasi@chromium.org>
Commit-Queue: Simran Basi <sbasi@chromium.org>
diff --git a/devserver.py b/devserver.py
index b9e0d18..d0f1e1d 100755
--- a/devserver.py
+++ b/devserver.py
@@ -693,16 +693,24 @@
updater.static_dir, kwargs['build'], kwargs['control_path'])
@cherrypy.expose
- def xbuddy_translate(self, *args):
+ def xbuddy_translate(self, *args, **kwargs):
"""Translates an xBuddy path to a real path to artifact if it exists.
Args:
- An xbuddy path in the form of {local|remote}/build_id/artifact.
+ args: An xbuddy path in the form of {local|remote}/build_id/artifact.
+ Local searches the devserver's static directory. Remote searches a
+ Google Storage image archive.
+
+ Kwargs:
+ image_dir: Google Storage image archive to search in if requesting a
+ remote artifact. If none uses the default bucket.
Returns:
- build_id/artifact
+ String in the format of build_id/artifact as stored on the local server
+ or in Google Storage.
"""
- build_id, filename = self._xbuddy.Translate(args)
+ build_id, filename = self._xbuddy.Translate(
+ args, image_dir=kwargs.get('image_dir'))
response = os.path.join(build_id, filename)
_Log('Path translation requested, returning: %s', response)
return response
diff --git a/devserver_constants.py b/devserver_constants.py
index aecd444..a1e4e7f 100644
--- a/devserver_constants.py
+++ b/devserver_constants.py
@@ -8,7 +8,7 @@
# 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'
+GS_LATEST_MASTER = '%(image_dir)s/%(board)s%(suffix)s/LATEST-master'
IMAGE_DIR = '%(board)s%(suffix)s/%(version)s'
GS_RELEASES_DIR = 'gs://chromeos-releases'
diff --git a/xbuddy.py b/xbuddy.py
index fb4b553..e635f9d 100644
--- a/xbuddy.py
+++ b/xbuddy.py
@@ -283,11 +283,29 @@
_Log("Path was rewritten to %s", rewrite)
return rewrite
- def _LookupOfficial(self, board, suffix=RELEASE):
+ @staticmethod
+ def _ResolveImageDir(image_dir):
+ """Clean up and return the image dir to use.
+
+ Args:
+ image_dir: directory in Google Storage to use.
+
+ Returns:
+ |image_dir| if |image_dir| is not None. Otherwise, returns
+ devserver_constants.GS_IMAGE_DIR
+ """
+ image_dir = image_dir or devserver_constants.GS_IMAGE_DIR
+ # Remove trailing slashes.
+ return image_dir.rstrip('/')
+
+ def _LookupOfficial(self, board, suffix=RELEASE, image_dir=None):
"""Check LATEST-master for the version number of interest."""
_Log("Checking gs for latest %s-%s image", board, suffix)
- latest_addr = devserver_constants.GS_LATEST_MASTER % {'board':board,
- 'suffix':suffix}
+ image_dir = XBuddy._ResolveImageDir(image_dir)
+ latest_addr = (devserver_constants.GS_LATEST_MASTER %
+ {'image_dir': image_dir,
+ 'board': board,
+ 'suffix': suffix})
cmd = 'gsutil cat %s' % latest_addr
msg = 'Failed to find build at %s' % latest_addr
# Full release + version is in the LATEST file.
@@ -296,7 +314,7 @@
return devserver_constants.IMAGE_DIR % {'board':board,
'suffix':suffix,
'version':version}
- def _LookupChannel(self, board, channel='stable'):
+ def _LookupChannel(self, board, channel='stable', image_dir=None):
"""Check the channel folder for the version number of interest."""
# Get all names in channel dir. Get 10 highest directories by version.
_Log("Checking channel '%s' for latest '%s' image", channel, board)
@@ -314,10 +332,11 @@
'board':board,
'suffix':RELEASE,
'version':'R*' + latest_version}
- image_dir = os.path.join(devserver_constants.GS_IMAGE_DIR, image_url)
+ image_dir = XBuddy._ResolveImageDir(image_dir)
+ gs_url = os.path.join(image_dir, image_url)
# There should only be one match on cros-image-archive.
- full_version = gsutil_util.GetLatestVersionFromGSDir(image_dir)
+ full_version = gsutil_util.GetLatestVersionFromGSDir(gs_url)
return devserver_constants.IMAGE_DIR % {'board':board,
'suffix':RELEASE,
@@ -364,7 +383,7 @@
raise XBuddyException('Could not find remote build_id for %s %s' % (
board, version))
- def _ResolveVersionToBuildId(self, board, version):
+ def _ResolveVersionToBuildId(self, board, version, image_dir=None):
"""Handle version aliases for remote payloads in GS.
Args:
@@ -375,6 +394,8 @@
2. latest-{channel}
3. latest-official-{board suffix}
4. version prefix (i.e. RX-Y.X, RX-Y, RX)
+ image_dir: image directory to check in Google Storage. If none,
+ the default bucket is used.
Returns:
Location where the image dir is actually found on GS (build_id)
@@ -389,20 +410,21 @@
return self._RemoteBuildId(board, version)
elif version == LATEST_OFFICIAL:
# latest-official --> LATEST build in board-release
- return self._LookupOfficial(board)
+ return self._LookupOfficial(board, image_dir=image_dir)
elif version_tuple[0] == LATEST_OFFICIAL:
# latest-official-{suffix} --> LATEST build in board-{suffix}
- return self._LookupOfficial(board, version_tuple[1])
+ return self._LookupOfficial(board, version_tuple[1], image_dir=image_dir)
elif version == LATEST:
# latest --> latest build on stable channel
- return self._LookupChannel(board)
+ return self._LookupChannel(board, image_dir=image_dir)
elif version_tuple[0] == LATEST:
if re.match(devserver_constants.VERSION_RE, version_tuple[1]):
# latest-R* --> most recent qualifying build
return self._LookupVersion(board, version_tuple[1])
else:
# latest-{channel} --> latest build within that channel
- return self._LookupChannel(board, version_tuple[1])
+ return self._LookupChannel(board, version_tuple[1],
+ image_dir=image_dir)
else:
# The given version doesn't match any known patterns.
raise XBuddyException("Version %s unknown. Can't find on GS." % version)
@@ -613,15 +635,23 @@
except Exception as err:
raise XBuddyException('Failed to clear %s: %s' % (clear_dir, err))
- def _GetFromGS(self, build_id, image_type):
+ def _GetFromGS(self, build_id, image_type, image_dir=None):
"""Check if the artifact is available locally. Download from GS if not.
+ Args:
+ build_id: Path to the image or update directory on the devserver or
+ in Google Storage. e.g. 'x86-generic/R26-4000.0.0'
+ image_type: Image type to download. Look at aliases at top of file for
+ options.
+ image_dir: Google Storage image archive to search in if requesting a
+ remote artifact. If none uses the default bucket.
+
Raises:
build_artifact.ArtifactDownloadError: If we failed to download the
artifact.
"""
- gs_url = os.path.join(devserver_constants.GS_IMAGE_DIR,
- build_id)
+ image_dir = XBuddy._ResolveImageDir(image_dir)
+ gs_url = os.path.join(image_dir, build_id)
# Stage image if not found in cache.
file_name = GS_ALIAS_TO_FILENAME[image_type]
@@ -634,15 +664,26 @@
else:
_Log('Image already cached.')
- def _GetArtifact(self, path_list, board=None, lookup_only=False):
+ def _GetArtifact(self, path_list, board=None, lookup_only=False,
+ image_dir=None):
"""Interpret an xBuddy path and return directory/file_name to resource.
Note board can be passed that in but by default if self._board is set,
that is used rather than board.
+ Args:
+ path_list: [board, version, alias] as split from the xbuddy call url.
+ board: Board whos artifacts we are looking for. If None, use the board
+ XBuddy was initialized to use.
+ lookup_only: If true just look up the artifact, if False stage it on
+ the devserver as well.
+ image_dir: Google Storage image archive to search in if requesting a
+ remote artifact. If none uses the default bucket.
+
Returns:
- build_id to the directory
- file_name of the artifact
+ build_id: Path to the image or update directory on the devserver or
+ in Google Storage. e.g. 'x86-generic/R26-4000.0.0'
+ file_name: of the artifact in the build_id directory.
Raises:
XBuddyException: if the path could not be translated
@@ -678,11 +719,12 @@
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._ResolveVersionToBuildId(board, version)
+ build_id = self._ResolveVersionToBuildId(board, version,
+ image_dir=image_dir)
_Log('Resolved version %s to %s.', version, build_id)
file_name = GS_ALIAS_TO_FILENAME[image_type]
if not lookup_only:
- self._GetFromGS(build_id, image_type)
+ self._GetFromGS(build_id, image_type, image_dir=image_dir)
return build_id, file_name
@@ -702,11 +744,18 @@
"""Returns the number of images cached by xBuddy."""
return str(self._Capacity())
- def Translate(self, path_list, board=None):
+ def Translate(self, path_list, board=None, image_dir=None):
"""Translates an xBuddy path to a real path to artifact if it exists.
Equivalent to the Get call, minus downloading and updating timestamps,
+ Args:
+ path_list: [board, version, alias] as split from the xbuddy call url.
+ board: Board whos artifacts we are looking for. If None, use the board
+ XBuddy was initialized to use.
+ image_dir: image directory to check in Google Storage. If none,
+ the default bucket is used.
+
Returns:
build_id: Path to the image or update directory on the devserver.
e.g. 'x86-generic/R26-4000.0.0'
@@ -722,7 +771,8 @@
"""
self._SyncRegistryWithBuildImages()
build_id, file_name = self._GetArtifact(path_list, board=board,
- lookup_only=True)
+ lookup_only=True,
+ image_dir=image_dir)
_Log('Returning path to payload: %s/%s', build_id, file_name)
return build_id, file_name
@@ -743,23 +793,25 @@
self._Download(gs_url, artifacts)
return build_id
- def Get(self, path_list):
+ def Get(self, path_list, image_dir=None):
"""The full xBuddy call, returns resource specified by path_list.
Please see devserver.py:xbuddy for full documentation.
Args:
- path_list: [board, version, alias] as split from the xbuddy call url
+ path_list: [board, version, alias] as split from the xbuddy call url.
+ image_dir: image directory to check in Google Storage. If none,
+ the default bucket is used.
Returns:
build_id: Path to the image or update directory on the devserver.
- e.g. 'x86-generic/R26-4000.0.0'
- The returned path is always the path to the directory within
- static_dir, so it is always the build_id of the image.
+ e.g. 'x86-generic/R26-4000.0.0'
+ The returned path is always the path to the directory within
+ static_dir, so it is always the build_id of the image.
file_name: The file name of the artifact. Can take any of the file
- values in devserver_constants.
- e.g. 'chromiumos_test_image.bin' or 'update.gz' if the path list
- specified 'test' or 'full_payload' artifacts, respectively.
+ values in devserver_constants.
+ e.g. 'chromiumos_test_image.bin' or 'update.gz' if the path list
+ specified 'test' or 'full_payload' artifacts, respectively.
Raises:
XBuddyException: if the path could not be translated
@@ -767,7 +819,7 @@
artifact.
"""
self._SyncRegistryWithBuildImages()
- build_id, file_name = self._GetArtifact(path_list)
+ build_id, file_name = self._GetArtifact(path_list, image_dir=image_dir)
Timestamp.UpdateTimestamp(self._timestamp_folder, build_id)
#TODO (joyc): run in sep thread
self.CleanCache()
diff --git a/xbuddy_unittest.py b/xbuddy_unittest.py
index 0436589..d4f90de 100755
--- a/xbuddy_unittest.py
+++ b/xbuddy_unittest.py
@@ -18,6 +18,10 @@
import xbuddy
#pylint: disable=W0212
+
+GS_ALTERNATE_DIR = 'gs://chromeos-alternate-archive/'
+
+
class xBuddyTest(mox.MoxTestBase):
"""Regression tests for xbuddy."""
def setUp(self):
@@ -65,7 +69,8 @@
gsutil_util.GetLatestVersionFromGSDir(mox.IgnoreArg()).AndReturn(mock_data2)
self.mox.ReplayAll()
expected = 'b-release/R28-4100.68.0'
- self.assertEqual(self.mock_xb._LookupChannel('b'), expected)
+ self.assertEqual(self.mock_xb._LookupChannel('b'),
+ expected)
self.mox.VerifyAll()
def testResolveVersionToBuildId_Official(self):
@@ -75,14 +80,22 @@
# 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.mock_xb._LookupOfficial(board, image_dir=None)
+ self.mock_xb._LookupOfficial(board,
+ image_dir=GS_ALTERNATE_DIR)
+ self.mock_xb._LookupOfficial(board, 'paladin', image_dir=None)
+ self.mock_xb._LookupOfficial(board, 'paladin',
+ image_dir=GS_ALTERNATE_DIR)
self.mox.ReplayAll()
version = 'latest-official'
self.mock_xb._ResolveVersionToBuildId(board, version)
+ self.mock_xb._ResolveVersionToBuildId(board, version,
+ image_dir=GS_ALTERNATE_DIR)
version = 'latest-official-paladin'
self.mock_xb._ResolveVersionToBuildId(board, version)
+ self.mock_xb._ResolveVersionToBuildId(board, version,
+ image_dir=GS_ALTERNATE_DIR)
self.mox.VerifyAll()
def testResolveVersionToBuildId_Channel(self):
@@ -91,14 +104,20 @@
# aliases that should be redirected to LookupChannel
self.mox.StubOutWithMock(self.mock_xb, '_LookupChannel')
- self.mock_xb._LookupChannel(board)
- self.mock_xb._LookupChannel(board, 'dev')
+ self.mock_xb._LookupChannel(board, image_dir=None)
+ self.mock_xb._LookupChannel(board, image_dir=GS_ALTERNATE_DIR)
+ self.mock_xb._LookupChannel(board, 'dev', image_dir=None)
+ self.mock_xb._LookupChannel(board, 'dev', image_dir=GS_ALTERNATE_DIR)
self.mox.ReplayAll()
version = 'latest'
self.mock_xb._ResolveVersionToBuildId(board, version)
+ self.mock_xb._ResolveVersionToBuildId(board, version,
+ image_dir=GS_ALTERNATE_DIR)
version = 'latest-dev'
self.mock_xb._ResolveVersionToBuildId(board, version)
+ self.mock_xb._ResolveVersionToBuildId(board, version,
+ image_dir=GS_ALTERNATE_DIR)
self.mox.VerifyAll()
def testBasicInterpretPath(self):