Merge "Return upload_results in upload-logs-swift role"
This commit is contained in:
commit
62fd612dc5
@ -24,9 +24,14 @@ import testtools
|
|||||||
import time
|
import time
|
||||||
import stat
|
import stat
|
||||||
import fixtures
|
import fixtures
|
||||||
|
try:
|
||||||
|
from unittest import mock
|
||||||
|
except ImportError:
|
||||||
|
import mock
|
||||||
|
|
||||||
|
import requests
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from .zuul_swift_upload import FileList, Indexer, FileDetail
|
from .zuul_swift_upload import FileList, Indexer, FileDetail, Uploader
|
||||||
|
|
||||||
|
|
||||||
FIXTURE_DIR = os.path.join(os.path.dirname(__file__),
|
FIXTURE_DIR = os.path.join(os.path.dirname(__file__),
|
||||||
@ -411,3 +416,57 @@ class TestFileDetail(testtools.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(time.gmtime(0), file_detail.last_modified)
|
self.assertEqual(time.gmtime(0), file_detail.last_modified)
|
||||||
self.assertEqual(0, file_detail.size)
|
self.assertEqual(0, file_detail.size)
|
||||||
|
|
||||||
|
|
||||||
|
class MockUploader(Uploader):
|
||||||
|
"""An uploader that uses a mocked cloud object"""
|
||||||
|
def __init__(self, container):
|
||||||
|
self.container = container
|
||||||
|
self.cloud = mock.Mock()
|
||||||
|
self.dry_run = False
|
||||||
|
self.public = True
|
||||||
|
self.delete_after = None
|
||||||
|
self.prefix = ""
|
||||||
|
self.url = 'http://dry-run-url.com/a/path/'
|
||||||
|
|
||||||
|
|
||||||
|
class TestUpload(testtools.TestCase):
|
||||||
|
|
||||||
|
def test_upload_result(self):
|
||||||
|
uploader = MockUploader(container="container")
|
||||||
|
|
||||||
|
# Let the upload method fail for the job-output.json, but not for the
|
||||||
|
# inventory.yaml file
|
||||||
|
def side_effect(container, name, **ignored):
|
||||||
|
if name == "job-output.json":
|
||||||
|
raise requests.exceptions.RequestException(
|
||||||
|
"Failed for a reason"
|
||||||
|
)
|
||||||
|
uploader.cloud.create_object = mock.Mock(
|
||||||
|
side_effect=side_effect
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get some test files to upload
|
||||||
|
files = [
|
||||||
|
FileDetail(
|
||||||
|
os.path.join(FIXTURE_DIR, "logs/job-output.json"),
|
||||||
|
"job-output.json",
|
||||||
|
),
|
||||||
|
FileDetail(
|
||||||
|
os.path.join(FIXTURE_DIR, "logs/zuul-info/inventory.yaml"),
|
||||||
|
"inventory.yaml",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
expected_failures = [
|
||||||
|
{
|
||||||
|
"file": "job-output.json",
|
||||||
|
"error": (
|
||||||
|
"Error posting file after multiple attempts: "
|
||||||
|
"Failed for a reason"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
failures = uploader.upload(files)
|
||||||
|
self.assertEqual(expected_failures, failures)
|
||||||
|
@ -682,20 +682,27 @@ class Uploader():
|
|||||||
|
|
||||||
num_threads = min(len(file_list), MAX_UPLOAD_THREADS)
|
num_threads = min(len(file_list), MAX_UPLOAD_THREADS)
|
||||||
threads = []
|
threads = []
|
||||||
|
# Keep track on upload failures
|
||||||
|
failures = []
|
||||||
|
|
||||||
queue = queuelib.Queue()
|
queue = queuelib.Queue()
|
||||||
# add items to queue
|
# add items to queue
|
||||||
for f in file_list:
|
for f in file_list:
|
||||||
queue.put(f)
|
queue.put(f)
|
||||||
|
|
||||||
for x in range(num_threads):
|
for x in range(num_threads):
|
||||||
t = threading.Thread(target=self.post_thread, args=(queue,))
|
t = threading.Thread(
|
||||||
|
target=self.post_thread, args=(queue, failures)
|
||||||
|
)
|
||||||
threads.append(t)
|
threads.append(t)
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
for t in threads:
|
for t in threads:
|
||||||
t.join()
|
t.join()
|
||||||
|
|
||||||
def post_thread(self, queue):
|
return failures
|
||||||
|
|
||||||
|
def post_thread(self, queue, failures):
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
file_detail = queue.get_nowait()
|
file_detail = queue.get_nowait()
|
||||||
@ -703,13 +710,23 @@ class Uploader():
|
|||||||
threading.current_thread(),
|
threading.current_thread(),
|
||||||
file_detail)
|
file_detail)
|
||||||
retry_function(lambda: self._post_file(file_detail))
|
retry_function(lambda: self._post_file(file_detail))
|
||||||
except requests.exceptions.RequestException:
|
except requests.exceptions.RequestException as e:
|
||||||
|
msg = "Error posting file after multiple attempts"
|
||||||
# Do our best to attempt to upload all the files
|
# Do our best to attempt to upload all the files
|
||||||
logging.exception("Error posting file after multiple attempts")
|
logging.exception(msg)
|
||||||
|
failures.append({
|
||||||
|
"file": file_detail.filename,
|
||||||
|
"error": "{}: {}".format(msg, e)
|
||||||
|
})
|
||||||
continue
|
continue
|
||||||
except IOError:
|
except IOError as e:
|
||||||
|
msg = "Error opening file"
|
||||||
# Do our best to attempt to upload all the files
|
# Do our best to attempt to upload all the files
|
||||||
logging.exception("Error opening file")
|
logging.exception(msg)
|
||||||
|
failures.append({
|
||||||
|
"file": file_detail.filename,
|
||||||
|
"error": "{}: {}".format(msg, e)
|
||||||
|
})
|
||||||
continue
|
continue
|
||||||
except queuelib.Empty:
|
except queuelib.Empty:
|
||||||
# No more work to do
|
# No more work to do
|
||||||
@ -801,8 +818,8 @@ def run(cloud, container, files,
|
|||||||
# Upload.
|
# Upload.
|
||||||
uploader = Uploader(cloud, container, prefix, delete_after,
|
uploader = Uploader(cloud, container, prefix, delete_after,
|
||||||
public, dry_run)
|
public, dry_run)
|
||||||
uploader.upload(file_list)
|
upload_failures = uploader.upload(file_list)
|
||||||
return uploader.url
|
return uploader.url, upload_failures
|
||||||
|
|
||||||
|
|
||||||
def ansible_main():
|
def ansible_main():
|
||||||
@ -825,7 +842,8 @@ def ansible_main():
|
|||||||
p = module.params
|
p = module.params
|
||||||
cloud = get_cloud(p.get('cloud'))
|
cloud = get_cloud(p.get('cloud'))
|
||||||
try:
|
try:
|
||||||
url = run(cloud, p.get('container'), p.get('files'),
|
url, upload_failures = run(
|
||||||
|
cloud, p.get('container'), p.get('files'),
|
||||||
indexes=p.get('indexes'),
|
indexes=p.get('indexes'),
|
||||||
parent_links=p.get('parent_links'),
|
parent_links=p.get('parent_links'),
|
||||||
topdir_parent_link=p.get('topdir_parent_link'),
|
topdir_parent_link=p.get('topdir_parent_link'),
|
||||||
@ -833,7 +851,8 @@ def ansible_main():
|
|||||||
footer=p.get('footer'),
|
footer=p.get('footer'),
|
||||||
delete_after=p.get('delete_after', 15552000),
|
delete_after=p.get('delete_after', 15552000),
|
||||||
prefix=p.get('prefix'),
|
prefix=p.get('prefix'),
|
||||||
public=p.get('public'))
|
public=p.get('public')
|
||||||
|
)
|
||||||
except (keystoneauth1.exceptions.http.HttpError,
|
except (keystoneauth1.exceptions.http.HttpError,
|
||||||
requests.exceptions.RequestException):
|
requests.exceptions.RequestException):
|
||||||
s = "Error uploading to %s.%s" % (cloud.name, cloud.config.region_name)
|
s = "Error uploading to %s.%s" % (cloud.name, cloud.config.region_name)
|
||||||
@ -844,8 +863,11 @@ def ansible_main():
|
|||||||
msg=s,
|
msg=s,
|
||||||
cloud=cloud.name,
|
cloud=cloud.name,
|
||||||
region_name=cloud.config.region_name)
|
region_name=cloud.config.region_name)
|
||||||
module.exit_json(changed=True,
|
module.exit_json(
|
||||||
url=url)
|
changed=True,
|
||||||
|
url=url,
|
||||||
|
upload_failures=upload_failures,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def cli_main():
|
def cli_main():
|
||||||
@ -903,7 +925,8 @@ def cli_main():
|
|||||||
if append_footer.lower() == 'none':
|
if append_footer.lower() == 'none':
|
||||||
append_footer = None
|
append_footer = None
|
||||||
|
|
||||||
url = run(get_cloud(args.cloud), args.container, args.files,
|
url, _ = run(
|
||||||
|
get_cloud(args.cloud), args.container, args.files,
|
||||||
indexes=not args.no_indexes,
|
indexes=not args.no_indexes,
|
||||||
parent_links=not args.no_parent_links,
|
parent_links=not args.no_parent_links,
|
||||||
topdir_parent_link=args.create_topdir_parent_link,
|
topdir_parent_link=args.create_topdir_parent_link,
|
||||||
@ -912,7 +935,8 @@ def cli_main():
|
|||||||
delete_after=args.delete_after,
|
delete_after=args.delete_after,
|
||||||
prefix=args.prefix,
|
prefix=args.prefix,
|
||||||
public=not args.no_public,
|
public=not args.no_public,
|
||||||
dry_run=args.dry_run)
|
dry_run=args.dry_run
|
||||||
|
)
|
||||||
print(url)
|
print(url)
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,10 +30,14 @@
|
|||||||
delete_after: "{{ zuul_log_delete_after | default(omit) }}"
|
delete_after: "{{ zuul_log_delete_after | default(omit) }}"
|
||||||
register: upload_results
|
register: upload_results
|
||||||
|
|
||||||
|
- block:
|
||||||
- name: Return log URL to Zuul
|
- name: Return log URL to Zuul
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
zuul_return:
|
zuul_return:
|
||||||
data:
|
data:
|
||||||
zuul:
|
zuul:
|
||||||
log_url: "{{ upload_results.url }}/"
|
log_url: "{{ upload_results.url }}/"
|
||||||
|
- name: Print upload failures
|
||||||
|
debug:
|
||||||
|
var: upload_results.upload_failures
|
||||||
when: upload_results is defined
|
when: upload_results is defined
|
||||||
|
Loading…
Reference in New Issue
Block a user