blob: 1997e6be75e9a7155e6339bda3e4ff0da3deb8cd [file] [log] [blame]
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Test the gslib module."""
from __future__ import print_function
import base64
import errno
import mox
import os
from chromite.lib import cros_build_lib
from chromite.lib import cros_test_lib
from chromite.lib import gs
from chromite.lib import osutils
from chromite.lib.paygen import gslib
# Typical output for a GS failure that is not our fault, and we should retry.
GS_RETRY_FAILURE = ('GSResponseError: status=403, code=InvalidAccessKeyId,'
'reason="Forbidden", message="Blah Blah Blah"')
# Typical output for a failure that we should not retry.
GS_DONE_FAILURE = ('AccessDeniedException:')
class TestGsLib(cros_test_lib.MoxTestCase):
"""Test gslib module."""
def setUp(self):
self.bucket_name = 'somebucket'
self.bucket_uri = 'gs://%s' % self.bucket_name
self.cmd_result = cros_build_lib.CommandResult()
self.cmd_error = cros_build_lib.RunCommandError('', self.cmd_result)
self.mox.StubOutWithMock(cros_build_lib, 'RunCommand')
# Because of autodetection, we no longer know which gsutil binary
# will be used.
self.gsutil = mox.IsA(str)
def testRetryGSLib(self):
"""Test our retry decorator"""
@gslib.RetryGSLib
def Success():
pass
@gslib.RetryGSLib
def SuccessArguments(arg1, arg2=False, arg3=False):
self.assertEqual(arg1, 1)
self.assertEqual(arg2, 2)
self.assertEqual(arg3, 3)
class RetryTestException(gslib.GSLibError):
"""Testing gslib.GSLibError exception for Retrying cases."""
def __init__(self):
super(RetryTestException, self).__init__(GS_RETRY_FAILURE)
class DoneTestException(gslib.GSLibError):
"""Testing gslib.GSLibError exception for Done cases."""
def __init__(self):
super(DoneTestException, self).__init__(GS_DONE_FAILURE)
@gslib.RetryGSLib
def Fail():
raise RetryTestException()
@gslib.RetryGSLib
def FailCount(counter, exception):
"""Pass in [count] times to fail before passing.
Using [] means the same object is used each retry, but it's contents
are mutable.
"""
counter[0] -= 1
if counter[0] >= 0:
raise exception()
if exception == RetryTestException:
# Make sure retries ran down to -1.
self.assertEquals(-1, counter[0])
Success()
SuccessArguments(1, 2, 3)
SuccessArguments(1, arg3=3, arg2=2)
FailCount([1], RetryTestException)
FailCount([2], RetryTestException)
self.assertRaises(RetryTestException, Fail)
self.assertRaises(DoneTestException, FailCount, [1], DoneTestException)
self.assertRaises(gslib.CopyFail, FailCount, [3], gslib.CopyFail)
self.assertRaises(gslib.CopyFail, FailCount, [4], gslib.CopyFail)
def testIsGsURI(self):
self.assertTrue(gslib.IsGsURI('gs://bucket/foo/bar'))
self.assertTrue(gslib.IsGsURI('gs://bucket'))
self.assertTrue(gslib.IsGsURI('gs://'))
self.assertFalse(gslib.IsGsURI('file://foo/bar'))
self.assertFalse(gslib.IsGsURI('/foo/bar'))
def testRunGsutilCommand(self):
args = ['TheCommand', 'Arg1', 'Arg2']
cmd = [self.gsutil] + args
# Set up the test replay script.
# Run 1.
cros_build_lib.RunCommand(
cmd, redirect_stdout=True, redirect_stderr=True).AndReturn(1)
# Run 2.
cros_build_lib.RunCommand(
cmd, redirect_stdout=False, redirect_stderr=True).AndReturn(2)
# Run 3.
cros_build_lib.RunCommand(cmd, redirect_stdout=True, redirect_stderr=True,
error_code_ok=True).AndReturn(3)
# Run 4.
cros_build_lib.RunCommand(
cmd, redirect_stdout=True, redirect_stderr=True).AndRaise(
self.cmd_error)
# Run 5.
cros_build_lib.RunCommand(
cmd, redirect_stdout=True, redirect_stderr=True).AndRaise(
OSError(errno.ENOENT, 'errmsg'))
self.mox.ReplayAll()
# Run the test verification.
self.assertEqual(1, gslib.RunGsutilCommand(args))
self.assertEqual(2, gslib.RunGsutilCommand(args, redirect_stdout=False))
self.assertEqual(3, gslib.RunGsutilCommand(args, error_code_ok=True))
self.assertRaises(gslib.GSLibError, gslib.RunGsutilCommand, args)
self.assertRaises(gslib.GsutilMissingError, gslib.RunGsutilCommand, args)
self.mox.VerifyAll()
def testCopy(self):
src_path = '/path/to/some/file'
dest_path = 'gs://bucket/some/gs/path'
# Set up the test replay script.
# Run 1, success.
cmd = [self.gsutil, 'cp', src_path, dest_path]
cros_build_lib.RunCommand(cmd, redirect_stdout=True, redirect_stderr=True)
# Run 2, failure.
error = cros_build_lib.RunCommandError(GS_RETRY_FAILURE, self.cmd_result)
for _ix in xrange(gslib.RETRY_ATTEMPTS + 1):
cmd = [self.gsutil, 'cp', src_path, dest_path]
cros_build_lib.RunCommand(
cmd, redirect_stdout=True, redirect_stderr=True).AndRaise(error)
self.mox.ReplayAll()
# Run the test verification.
gslib.Copy(src_path, dest_path)
self.assertRaises(gslib.CopyFail, gslib.Copy, src_path, dest_path)
self.mox.VerifyAll()
def testMove(self):
src_path = 'gs://bucket/some/gs/path'
dest_path = '/some/other/path'
# Set up the test replay script.
cmd = [self.gsutil, 'mv', src_path, dest_path]
cros_build_lib.RunCommand(cmd, redirect_stdout=True, redirect_stderr=True)
self.mox.ReplayAll()
# Run the test verification.
gslib.Move(src_path, dest_path)
self.mox.VerifyAll()
def testRemove(self):
path1 = 'gs://bucket/some/gs/path'
path2 = 'gs://bucket/some/other/path'
# Set up the test replay script.
# Run 1, one path.
cros_build_lib.RunCommand([self.gsutil, 'rm', path1],
redirect_stdout=True, redirect_stderr=True)
# Run 2, two paths.
cros_build_lib.RunCommand([self.gsutil, 'rm', path1, path2],
redirect_stdout=True, redirect_stderr=True)
# Run 3, one path, recursive.
cros_build_lib.RunCommand([self.gsutil, 'rm', '-R', path1],
redirect_stdout=True, redirect_stderr=True)
self.mox.ReplayAll()
# Run the test verification.
gslib.Remove(path1)
gslib.Remove(path1, path2)
gslib.Remove(path1, recurse=True)
self.mox.VerifyAll()
def testRemoveNoMatch(self):
path = 'gs://bucket/some/gs/path'
# Set up the test replay script.
cmd = [self.gsutil, 'rm', path]
cros_build_lib.RunCommand(cmd, redirect_stdout=True, redirect_stderr=True)
self.mox.ReplayAll()
# Run the test verification.
gslib.Remove(path, ignore_no_match=True)
self.mox.VerifyAll()
def testRemoveFail(self):
path = 'gs://bucket/some/gs/path'
# Set up the test replay script.
cmd = [self.gsutil, 'rm', path]
error = cros_build_lib.RunCommandError(GS_RETRY_FAILURE, self.cmd_result)
for _ix in xrange(gslib.RETRY_ATTEMPTS + 1):
cros_build_lib.RunCommand(
cmd, redirect_stdout=True, redirect_stderr=True).AndRaise(error)
self.mox.ReplayAll()
# Run the test verification.
self.assertRaises(gslib.RemoveFail,
gslib.Remove, path)
self.mox.VerifyAll()
def testRemoveNotFoundExceptionIgnoreNoMatch(self):
"""Verify that setting ignore_no_match to True ignores NotFoundExceptions"""
path = 'gs://bucket/some/gs/path/that/totally/does/not/exist'
# Set up the test replay script.
cmd = [self.gsutil, 'rm', '-R', path]
failure_msg = 'Removing ' + path + '...\nNotFoundException: 404 ' + path
self.cmd_result.error = failure_msg
error = cros_build_lib.RunCommandError(failure_msg, self.cmd_result)
cros_build_lib.RunCommand(cmd, redirect_stdout=True,
redirect_stderr=True).AndRaise(error)
self.mox.ReplayAll()
# Run the test verification.
gslib.Remove(path, recurse=True, ignore_no_match=True)
self.mox.VerifyAll()
def testRemoveNotFoundException(self):
"""Verify that a NotFoundException results in a failure."""
path = 'gs://bucket/some/gs/path/that/totally/does/not/exist'
# Set up the test replay script.
cmd = [self.gsutil, 'rm', path]
failure_msg = 'Removing ' + path + '...\nNotFoundException: 404 ' + path
self.cmd_result.error = failure_msg
error = cros_build_lib.RunCommandError(failure_msg, self.cmd_result)
for _ix in xrange(gslib.RETRY_ATTEMPTS + 1):
cros_build_lib.RunCommand(cmd, redirect_stdout=True,
redirect_stderr=True).AndRaise(error)
self.mox.ReplayAll()
# Run the test verification.
self.assertRaises(gslib.RemoveFail, gslib.Remove, path)
self.mox.VerifyAll()
def testCreateWithContents(self):
gs_path = 'gs://chromeos-releases-test/create-with-contents-test'
contents = 'Stuff with Rocks In'
self.mox.StubOutWithMock(gslib, 'Copy')
gslib.Copy(mox.IsA(str), gs_path)
self.mox.ReplayAll()
gslib.CreateWithContents(gs_path, contents)
self.mox.VerifyAll()
def testCat(self):
path = 'gs://bucket/some/gs/path'
# Set up the test replay script.
cmd = [self.gsutil, 'cat', path]
result = cros_test_lib.EasyAttr(error='', output='TheContent')
cros_build_lib.RunCommand(
cmd, redirect_stdout=True, redirect_stderr=True).AndReturn(result)
self.mox.ReplayAll()
# Run the test verification.
result = gslib.Cat(path)
self.assertEquals('TheContent', result)
self.mox.VerifyAll()
def testCatFail(self):
path = 'gs://bucket/some/gs/path'
# Set up the test replay script.
cmd = [self.gsutil, 'cat', path]
for _ix in xrange(gslib.RETRY_ATTEMPTS + 1):
cros_build_lib.RunCommand(
cmd, redirect_stdout=True, redirect_stderr=True).AndRaise(
self.cmd_error)
self.mox.ReplayAll()
# Run the test verification.
self.assertRaises(gslib.CatFail, gslib.Cat, path)
self.mox.VerifyAll()
def testStat(self):
path = 'gs://bucket/some/gs/path'
# Set up the test replay script.
cmd = [self.gsutil, 'stat', path]
cros_build_lib.RunCommand(cmd, redirect_stdout=True,
redirect_stderr=True).AndReturn(self.cmd_result)
self.mox.ReplayAll()
# Run the test verification.
self.assertIs(gslib.Stat(path), None)
self.mox.VerifyAll()
def testStatFail(self):
path = 'gs://bucket/some/gs/path'
# Set up the test replay script.
cmd = [self.gsutil, 'stat', path]
cros_build_lib.RunCommand(
cmd, redirect_stdout=True, redirect_stderr=True).AndRaise(
self.cmd_error)
self.mox.ReplayAll()
# Run the test verification.
self.assertRaises(gslib.StatFail, gslib.Stat, path)
self.mox.VerifyAll()
def testFileSize(self):
gs_uri = '%s/%s' % (self.bucket_uri, 'some/file/path')
# Set up the test replay script.
cmd = [self.gsutil, '-d', 'stat', gs_uri]
size = 96
output = '\n'.join(['header: x-goog-generation: 1386322968237000',
'header: x-goog-metageneration: 1',
'header: x-goog-stored-content-encoding: identity',
'header: x-goog-stored-content-length: %d' % size,
'header: Content-Type: application/octet-stream'])
cros_build_lib.RunCommand(
cmd, redirect_stdout=True, redirect_stderr=True).AndReturn(
cros_test_lib.EasyAttr(output=output))
self.mox.ReplayAll()
# Run the test verification.
result = gslib.FileSize(gs_uri)
self.assertEqual(size, result)
self.mox.VerifyAll()
def _TestCatWithHeaders(self, gs_uri, cmd_output, cmd_error):
# Set up the test replay script.
# Run 1, versioning not enabled in bucket, one line of output.
cmd = ['gsutil', '-d', 'cat', gs_uri]
cmd_result = cros_test_lib.EasyAttr(output=cmd_output,
error=cmd_error,
cmdstr=' '.join(cmd))
cmd[0] = mox.IsA(str)
cros_build_lib.RunCommand(
cmd, redirect_stdout=True, redirect_stderr=True).AndReturn(cmd_result)
self.mox.ReplayAll()
def testCatWithHeaders(self):
gs_uri = '%s/%s' % (self.bucket_uri, 'some/file/path')
generation = 123454321
metageneration = 2
error = '\n'.join([
'header: x-goog-generation: %d' % generation,
'header: x-goog-metageneration: %d' % metageneration,
])
expected_output = 'foo'
self._TestCatWithHeaders(gs_uri, expected_output, error)
# Run the test verification.
headers = {}
result = gslib.Cat(gs_uri, headers=headers)
self.assertEqual(generation, int(headers['generation']))
self.assertEqual(metageneration, int(headers['metageneration']))
self.assertEqual(result, expected_output)
self.mox.VerifyAll()
def testFileSizeNoSuchFile(self):
gs_uri = '%s/%s' % (self.bucket_uri, 'some/file/path')
# Set up the test replay script.
cmd = [self.gsutil, '-d', 'stat', gs_uri]
for _ in xrange(0, gslib.RETRY_ATTEMPTS + 1):
cros_build_lib.RunCommand(
cmd, redirect_stdout=True, redirect_stderr=True).AndRaise(
self.cmd_error)
self.mox.ReplayAll()
# Run the test verification.
self.assertRaises(gslib.URIError, gslib.FileSize, gs_uri)
self.mox.VerifyAll()
def testListFiles(self):
files = [
'%s/some/path' % self.bucket_uri,
'%s/some/file/path' % self.bucket_uri,
]
directories = [
'%s/some/dir/' % self.bucket_uri,
'%s/some/dir/path/' % self.bucket_uri,
]
gs_uri = '%s/**' % self.bucket_uri
cmd = [self.gsutil, 'ls', gs_uri]
# Prepare cmd_result for a good run.
# Fake a trailing empty line.
output = '\n'.join(files + directories + [''])
cmd_result_ok = cros_test_lib.EasyAttr(output=output, returncode=0)
# Prepare exception for a run that finds nothing.
stderr = 'CommandException: One or more URLs matched no objects.\n'
cmd_result_empty = cros_build_lib.CommandResult(error=stderr)
empty_exception = cros_build_lib.RunCommandError(stderr, cmd_result_empty)
# Prepare exception for a run that triggers a GS failure.
error = cros_build_lib.RunCommandError(GS_RETRY_FAILURE, self.cmd_result)
# Set up the test replay script.
# Run 1, runs ok.
cros_build_lib.RunCommand(
cmd, redirect_stdout=True, redirect_stderr=True).AndReturn(
cmd_result_ok)
# Run 2, runs ok, sorts files.
cros_build_lib.RunCommand(
cmd, redirect_stdout=True, redirect_stderr=True).AndReturn(
cmd_result_ok)
# Run 3, finds nothing.
cros_build_lib.RunCommand(
cmd, redirect_stdout=True, redirect_stderr=True).AndRaise(
empty_exception)
# Run 4, failure in GS.
for _ix in xrange(gslib.RETRY_ATTEMPTS + 1):
cros_build_lib.RunCommand(
cmd, redirect_stdout=True, redirect_stderr=True).AndRaise(error)
self.mox.ReplayAll()
# Run the test verification.
result = gslib.ListFiles(self.bucket_uri, recurse=True)
self.assertEqual(files, result)
result = gslib.ListFiles(self.bucket_uri, recurse=True, sort=True)
self.assertEqual(sorted(files), result)
result = gslib.ListFiles(self.bucket_uri, recurse=True)
self.assertEqual([], result)
self.assertRaises(gslib.GSLibError, gslib.ListFiles,
self.bucket_uri, recurse=True)
self.mox.VerifyAll()
def testCmp(self):
uri1 = 'gs://some/gs/path'
uri2 = 'gs://some/other/path'
local_path = '/some/local/path'
md5 = 'TheMD5Sum'
self.mox.StubOutWithMock(gslib, 'MD5Sum')
self.mox.StubOutWithMock(gslib.filelib, 'MD5Sum')
# Set up the test replay script.
# Run 1, same md5, both GS.
gslib.MD5Sum(uri1).AndReturn(md5)
gslib.MD5Sum(uri2).AndReturn(md5)
# Run 2, different md5, both GS.
gslib.MD5Sum(uri1).AndReturn(md5)
gslib.MD5Sum(uri2).AndReturn('Other' + md5)
# Run 3, same md5, one GS on local.
gslib.MD5Sum(uri1).AndReturn(md5)
gslib.filelib.MD5Sum(local_path).AndReturn(md5)
# Run 4, different md5, one GS on local.
gslib.MD5Sum(uri1).AndReturn(md5)
gslib.filelib.MD5Sum(local_path).AndReturn('Other' + md5)
# Run 5, missing file, both GS.
gslib.MD5Sum(uri1).AndReturn(None)
# Run 6, args are None.
gslib.filelib.MD5Sum(None).AndReturn(None)
self.mox.ReplayAll()
# Run the test verification.
self.assertTrue(gslib.Cmp(uri1, uri2))
self.assertFalse(gslib.Cmp(uri1, uri2))
self.assertTrue(gslib.Cmp(uri1, local_path))
self.assertFalse(gslib.Cmp(uri1, local_path))
self.assertFalse(gslib.Cmp(uri1, uri2))
self.assertFalse(gslib.Cmp(None, None))
self.mox.VerifyAll()
def testMD5SumAccessError(self):
gs_uri = 'gs://bucket/foo/bar/somefile'
crc32c = 'c96fd51e'
crc32c_64 = base64.b64encode(base64.b16decode(crc32c, casefold=True))
md5_sum = 'b026324c6904b2a9cb4b88d6d61c81d1'
md5_sum_64 = base64.b64encode(base64.b16decode(md5_sum, casefold=True))
output = '\n'.join([
'%s:' % gs_uri,
' Creation time: Tue, 04 Mar 2014 19:55:26 GMT',
' Content-Language: en',
' Content-Length: 2',
' Content-Type: application/octet-stream',
' Hash (crc32c): %s' % crc32c_64,
' Hash (md5): %s' % md5_sum_64,
' ETag: CMi938jU+bwCEAE=',
' Generation: 1393962926989000',
' Metageneration: 1',
' ACL: ACCESS DENIED. Note: you need OWNER '
'permission',
' on the object to read its ACL.',
])
# Set up the test replay script.
cmd = [self.gsutil, 'ls', '-L', gs_uri]
cros_build_lib.RunCommand(
cmd, redirect_stdout=True, redirect_stderr=True,
error_code_ok=True).AndReturn(
cros_test_lib.EasyAttr(output=output))
self.mox.ReplayAll()
# Run the test verification.
result = gslib.MD5Sum(gs_uri)
self.assertEqual(md5_sum, result)
self.mox.VerifyAll()
def testMD5SumAccessOK(self):
gs_uri = 'gs://bucket/foo/bar/somefile'
crc32c = 'c96fd51e'
crc32c_64 = base64.b64encode(base64.b16decode(crc32c, casefold=True))
md5_sum = 'b026324c6904b2a9cb4b88d6d61c81d1'
md5_sum_64 = base64.b64encode(base64.b16decode(md5_sum, casefold=True))
output = '\n'.join([
'%s:' % gs_uri,
' Creation time: Tue, 04 Mar 2014 19:55:26 GMT',
' Content-Language: en',
' Content-Length: 2',
' Content-Type: application/octet-stream',
' Hash (crc32c): %s' % crc32c_64,
' Hash (md5): %s' % md5_sum_64,
' ETag: CMi938jU+bwCEAE=',
' Generation: 1393962926989000',
' Metageneration: 1',
' ACL: [',
' {',
' "entity": "project-owners-134157665460",',
' "projectTeam": {',
' "projectNumber": "134157665460",',
' "team": "owners"',
' },',
' "role": "OWNER"',
' }',
']',
])
# Set up the test replay script.
cmd = [self.gsutil, 'ls', '-L', gs_uri]
cros_build_lib.RunCommand(
cmd, redirect_stdout=True, redirect_stderr=True,
error_code_ok=True).AndReturn(
cros_test_lib.EasyAttr(output=output))
self.mox.ReplayAll()
# Run the test verification.
result = gslib.MD5Sum(gs_uri)
self.assertEqual(md5_sum, result)
self.mox.VerifyAll()
class TestGsLibAccess(cros_test_lib.MoxTempDirTestCase):
"""Test access to gs lib functionality.
The tests here require GS .boto access to the gs://chromeos-releases-public
bucket, which is world-readable. Any .boto setup should do, but without
a .boto there will be failures.
"""
def populateUri(self, uri):
local_path = os.path.join(self.tempdir, 'remote_content')
osutils.WriteFile(local_path, 'some sample content')
gslib.Copy(local_path, uri)
return local_path
@cros_test_lib.NetworkTest()
def testCopyAndMD5Sum(self):
"""Higher-level functional test. Test MD5Sum OK."""
with gs.TemporaryURL('chromite.gslib.md5') as tempuri:
local_path = self.populateUri(tempuri)
local_md5 = gslib.filelib.MD5Sum(local_path)
gs_md5 = gslib.MD5Sum(tempuri)
self.assertEqual(gs_md5, local_md5)
@cros_test_lib.NetworkTest()
def testExists(self):
with gs.TemporaryURL('chromite.gslib.exists') as tempuri:
self.populateUri(tempuri)
self.assertTrue(gslib.Exists(tempuri))
bogus_gs_path = 'gs://chromeos-releases-test/bogus/non-existent-url'
self.assertFalse(gslib.Exists(bogus_gs_path))
@cros_test_lib.NetworkTest()
def testExistsFalse(self):
"""Test Exists logic with non-standard output from gsutil."""
expected_output = ('GSResponseError: status=404, code=NoSuchKey,'
' reason="Not Found",'
' message="The specified key does not exist."')
err1 = gslib.StatFail(expected_output)
err2 = gslib.StatFail('You are using a deprecated alias, "getacl",'
'for the "acl" command.\n' +
expected_output)
uri = 'gs://any/fake/uri/will/do'
cmd = ['stat', uri]
self.mox.StubOutWithMock(gslib, 'RunGsutilCommand')
# Set up the test replay script.
# Run 1, normal.
gslib.RunGsutilCommand(cmd, failed_exception=gslib.StatFail,
get_headers_from_stdout=True).AndRaise(err1)
# Run 2, extra output.
gslib.RunGsutilCommand(cmd, failed_exception=gslib.StatFail,
get_headers_from_stdout=True).AndRaise(err2)
self.mox.ReplayAll()
# Run the test verification
self.assertFalse(gslib.Exists(uri))
self.assertFalse(gslib.Exists(uri))
self.mox.VerifyAll()
@cros_test_lib.NetworkTest()
def testMD5SumBadPath(self):
"""Higher-level functional test. Test MD5Sum bad path:
1) Make up random, non-existent gs path
2) Ask for MD5Sum. Make sure it fails, but with no exeption.
"""
gs_path = 'gs://chromeos-releases/awsedrftgyhujikol'
gs_md5 = gslib.MD5Sum(gs_path)
self.assertTrue(gs_md5 is None)
@cros_test_lib.NetworkTest()
def testMD5SumBadBucket(self):
"""Higher-level functional test. Test MD5Sum bad bucket:
1) Make up random, non-existent gs bucket and path
2) Ask for MD5Sum. Make sure it fails, with exception
"""
gs_path = 'gs://lokijuhygtfrdesxcv/awsedrftgyhujikol'
gs_md5 = gslib.MD5Sum(gs_path)
self.assertTrue(gs_md5 is None)