Add GUI logging workflows
The workflow works like this: 1. Drain the tripleo Zaqar queue 2. Format the messages as a log string 3. Publish the string to a swift object If the swift object exceeds 10MB, we rotate it. This patch also includes a workflow to download the latest log via a temporary url. The download log action shares a lot of code with the plan export action, so the common bits were extracted. Implements: spec gui-logging Implements: blueprint websocket-logging Change-Id: I2affd39e85ccfdbaa18590de182104715cfbbed4
This commit is contained in:
parent
fa0b9f5208
commit
4cd60846af
5
releasenotes/notes/gui-logging-5413d0d86e618c59.yaml
Normal file
5
releasenotes/notes/gui-logging-5413d0d86e618c59.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
GUI logging - we added actions and workflows to support processing and
|
||||
storage of logging data from tripleo-ui
|
@ -94,6 +94,9 @@ mistral.actions =
|
||||
tripleo.plan.delete = tripleo_common.actions.plan:DeletePlanAction
|
||||
tripleo.plan.list = tripleo_common.actions.plan:ListPlansAction
|
||||
tripleo.plan.export = tripleo_common.actions.plan:ExportPlanAction
|
||||
tripleo.logging_to_swift.format_messages = tripleo_common.actions.logging_to_swift:FormatMessagesAction
|
||||
tripleo.logging_to_swift.publish_ui_log_to_swift = tripleo_common.actions.logging_to_swift:PublishUILogToSwiftAction
|
||||
tripleo.logging_to_swift.prepare_log_download = tripleo_common.actions.logging_to_swift:PrepareLogDownloadAction
|
||||
tripleo.role.list = tripleo_common.actions.plan:ListRolesAction
|
||||
tripleo.scale.delete_node = tripleo_common.actions.scale:ScaleDownAction
|
||||
tripleo.swift.tempurl = tripleo_common.actions.swifthelper:SwiftTempUrlAction
|
||||
|
183
tripleo_common/actions/logging_to_swift.py
Normal file
183
tripleo_common/actions/logging_to_swift.py
Normal file
@ -0,0 +1,183 @@
|
||||
# Copyright 2017 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from datetime import datetime
|
||||
import json
|
||||
import logging
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from mistral_lib import actions
|
||||
from oslo_concurrency import processutils
|
||||
from swiftclient import exceptions as swiftexceptions
|
||||
|
||||
from tripleo_common.actions import base
|
||||
from tripleo_common import constants
|
||||
from tripleo_common.utils import swift as swiftutils
|
||||
from tripleo_common.utils import time_functions as timeutils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FormatMessagesAction(actions.Action):
|
||||
"""Format messages as logs
|
||||
|
||||
Given a list of Zaqar messages from the TripleO UI, return a log-formatted
|
||||
string
|
||||
"""
|
||||
def __init__(self, messages):
|
||||
super(FormatMessagesAction, self).__init__()
|
||||
self.messages = messages
|
||||
|
||||
def run(self, context):
|
||||
lines = []
|
||||
|
||||
for zaqar_message in self.messages:
|
||||
log_object = zaqar_message.get('body')
|
||||
|
||||
if not log_object:
|
||||
continue
|
||||
|
||||
body = log_object.get('message', '')
|
||||
level = log_object.get('level', 'info')
|
||||
timestamp = log_object.get('timestamp', datetime.utcnow())
|
||||
|
||||
if isinstance(body, (dict, list,)):
|
||||
body = json.dumps(body)
|
||||
|
||||
lines.append(
|
||||
'{date} {level} {body}'.format(
|
||||
date=timeutils.epoch_to_formatted_date(timestamp),
|
||||
level=level,
|
||||
body=body
|
||||
)
|
||||
)
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
class PublishUILogToSwiftAction(base.TripleOAction):
|
||||
"""Publish logs from UI to Swift"""
|
||||
|
||||
def __init__(self, logging_data, logging_container):
|
||||
super(PublishUILogToSwiftAction, self).__init__()
|
||||
self.logging_data = logging_data
|
||||
self.logging_container = logging_container
|
||||
|
||||
def _rotate(self, swift):
|
||||
"""Optimistic log rotation
|
||||
|
||||
Failure to sucessfully complete log rotation doesn't cause the
|
||||
entire action to fail
|
||||
"""
|
||||
try:
|
||||
headers = swift.head_object(self.logging_container,
|
||||
constants.TRIPLEO_UI_LOG_FILENAME)
|
||||
if headers['content-length'] < constants.TRIPLEO_UI_LOG_FILE_SIZE:
|
||||
LOG.debug("Log file hasn't reached a full size so it doesn't"
|
||||
" need to be rotated.")
|
||||
return
|
||||
except swiftexceptions.ClientException:
|
||||
LOG.debug("Couldn't get existing log file, skip log rotation.")
|
||||
return
|
||||
|
||||
try:
|
||||
files = swift.get_container(self.logging_container)[1]
|
||||
except swiftexceptions.ClientException:
|
||||
LOG.warn("Logging container doesn't exist, skip log rotation.")
|
||||
return
|
||||
|
||||
largest_existing_suffix = 0
|
||||
|
||||
for f in files:
|
||||
try:
|
||||
suffix = int(f['name'].split('.')[-1])
|
||||
if suffix > largest_existing_suffix:
|
||||
largest_existing_suffix = suffix
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
next_suffix = largest_existing_suffix + 1
|
||||
next_filename = '{}.{}'.format(
|
||||
constants.TRIPLEO_UI_LOG_FILENAME, next_suffix)
|
||||
try:
|
||||
data = swift.get_object(self.logging_container,
|
||||
constants.TRIPLEO_UI_LOG_FILENAME)[1]
|
||||
swift.put_object(self.logging_container, next_filename, data)
|
||||
swift.delete_object(self.logging_container,
|
||||
constants.TRIPLEO_UI_LOG_FILENAME)
|
||||
except swiftexceptions.ClientException as err:
|
||||
msg = "Log rotation failed: %s" % err
|
||||
LOG.warn(msg)
|
||||
|
||||
def run(self, context):
|
||||
swift = self.get_object_client(context)
|
||||
swiftutils.get_or_create_container(swift, self.logging_container)
|
||||
self._rotate(swift)
|
||||
|
||||
try:
|
||||
old_contents = swift.get_object(
|
||||
self.logging_container,
|
||||
constants.TRIPLEO_UI_LOG_FILENAME)[1]
|
||||
new_contents = old_contents + '\n' + self.logging_data
|
||||
except swiftexceptions.ClientException:
|
||||
LOG.debug(
|
||||
"There is no existing logging data, starting a new file.")
|
||||
new_contents = self.logging_data
|
||||
|
||||
try:
|
||||
swift.put_object(self.logging_container,
|
||||
constants.TRIPLEO_UI_LOG_FILENAME,
|
||||
new_contents)
|
||||
except swiftexceptions.ClientException as err:
|
||||
msg = "Failed to publish logs: %s" % err
|
||||
return actions.Result(error=msg)
|
||||
|
||||
|
||||
class PrepareLogDownloadAction(base.TripleOAction):
|
||||
"""Publish all GUI logs to a temporary URL"""
|
||||
|
||||
def __init__(self, logging_container, downloads_container, delete_after):
|
||||
super(PrepareLogDownloadAction, self).__init__()
|
||||
self.logging_container = logging_container
|
||||
self.downloads_container = downloads_container
|
||||
self.delete_after = delete_after
|
||||
|
||||
def run(self, context):
|
||||
swift = self.get_object_client(context)
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
tarball_name = 'logs-%s.tar.gz' % timeutils.timestamp()
|
||||
|
||||
try:
|
||||
swiftutils.download_container(
|
||||
swift, self.logging_container, tmp_dir)
|
||||
swiftutils.create_and_upload_tarball(
|
||||
swift, tmp_dir, self.downloads_container,
|
||||
tarball_name, self.delete_after)
|
||||
except swiftexceptions.ClientException as err:
|
||||
msg = "Error attempting an operation on container: %s" % err
|
||||
return actions.Result(error=msg)
|
||||
except (OSError, IOError) as err:
|
||||
msg = "Error while writing file: %s" % err
|
||||
return actions.Result(error=msg)
|
||||
except processutils.ProcessExecutionError as err:
|
||||
msg = "Error while creating a tarball: %s" % err
|
||||
return actions.Result(error=msg)
|
||||
except Exception as err:
|
||||
msg = "Error exporting logs: %s" % err
|
||||
return actions.Result(error=msg)
|
||||
finally:
|
||||
shutil.rmtree(tmp_dir)
|
||||
|
||||
return tarball_name
|
@ -13,7 +13,6 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import yaml
|
||||
@ -31,7 +30,6 @@ from tripleo_common import constants
|
||||
from tripleo_common import exception
|
||||
from tripleo_common.utils import plan as plan_utils
|
||||
from tripleo_common.utils import swift as swiftutils
|
||||
from tripleo_common.utils import tarball
|
||||
from tripleo_common.utils.validations import pattern_validator
|
||||
|
||||
|
||||
@ -221,46 +219,16 @@ class ExportPlanAction(base.TripleOAction):
|
||||
self.delete_after = delete_after
|
||||
self.exports_container = exports_container
|
||||
|
||||
def _download_templates(self, swift, tmp_dir):
|
||||
"""Download templates to a temp folder."""
|
||||
template_files = swift.get_container(self.plan)[1]
|
||||
|
||||
for tf in template_files:
|
||||
filename = tf['name']
|
||||
contents = swift.get_object(self.plan, filename)[1]
|
||||
path = os.path.join(tmp_dir, filename)
|
||||
dirname = os.path.dirname(path)
|
||||
|
||||
if not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
|
||||
with open(path, 'w') as f:
|
||||
f.write(contents)
|
||||
|
||||
def _create_and_upload_tarball(self, swift, tmp_dir):
|
||||
"""Create a tarball containing the tmp_dir and upload it to Swift."""
|
||||
tarball_name = '%s.tar.gz' % self.plan
|
||||
headers = {'X-Delete-After': self.delete_after}
|
||||
|
||||
# make sure the root container which holds all plan exports exists
|
||||
try:
|
||||
swift.get_container(self.exports_container)
|
||||
except swiftexceptions.ClientException:
|
||||
swift.put_container(self.exports_container)
|
||||
|
||||
with tempfile.NamedTemporaryFile() as tmp_tarball:
|
||||
tarball.create_tarball(tmp_dir, tmp_tarball.name)
|
||||
|
||||
swift.put_object(self.exports_container, tarball_name, tmp_tarball,
|
||||
headers=headers)
|
||||
|
||||
def run(self, context):
|
||||
swift = self.get_object_client(context)
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
tarball_name = '%s.tar.gz' % self.plan
|
||||
|
||||
try:
|
||||
self._download_templates(swift, tmp_dir)
|
||||
self._create_and_upload_tarball(swift, tmp_dir)
|
||||
swiftutils.download_container(swift, self.plan, tmp_dir)
|
||||
swiftutils.create_and_upload_tarball(
|
||||
swift, tmp_dir, self.exports_container, tarball_name,
|
||||
self.delete_after)
|
||||
except swiftexceptions.ClientException as err:
|
||||
msg = "Error attempting an operation on container: %s" % err
|
||||
return actions.Result(error=msg)
|
||||
|
@ -127,3 +127,6 @@ DEFAULT_DEPLOY_RAMDISK_NAME = 'bm-deploy-ramdisk'
|
||||
|
||||
# The name for the swift container to host the cache for tripleo
|
||||
TRIPLEO_CACHE_CONTAINER = "__cache__"
|
||||
|
||||
TRIPLEO_UI_LOG_FILE_SIZE = 1e7 # 10MB
|
||||
TRIPLEO_UI_LOG_FILENAME = 'tripleo-ui.logs'
|
||||
|
161
tripleo_common/tests/actions/test_logging_to_swift.py
Normal file
161
tripleo_common/tests/actions/test_logging_to_swift.py
Normal file
@ -0,0 +1,161 @@
|
||||
# Copyright 2017 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import mock
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
|
||||
from tripleo_common.actions import logging_to_swift
|
||||
from tripleo_common.tests import base
|
||||
|
||||
|
||||
class LogFormattingTest(base.TestCase):
|
||||
|
||||
def test_log_formatting(self):
|
||||
messages = [
|
||||
{
|
||||
'body': {
|
||||
'message': 'Test 1',
|
||||
'level': 'INFO',
|
||||
'timestamp': '1496322000000'
|
||||
}
|
||||
},
|
||||
{
|
||||
'body': {
|
||||
'message': 'Test 2',
|
||||
'level': 'WARN',
|
||||
'timestamp': '1496329200000'
|
||||
}
|
||||
}
|
||||
]
|
||||
action = logging_to_swift.FormatMessagesAction(messages)
|
||||
result = action.run({})
|
||||
self.assertEqual(result, ('2017-06-01 13:00:00 INFO Test 1\n'
|
||||
'2017-06-01 15:00:00 WARN Test 2'))
|
||||
|
||||
|
||||
class PublishUILogToSwiftActionTest(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(PublishUILogToSwiftActionTest, self).setUp()
|
||||
self.container = 'container'
|
||||
self.swift = mock.MagicMock()
|
||||
swift_patcher = mock.patch(
|
||||
'tripleo_common.actions.base.TripleOAction.get_object_client',
|
||||
return_value=self.swift)
|
||||
swift_patcher.start()
|
||||
self.addCleanup(swift_patcher.stop)
|
||||
self.ctx = mock.MagicMock()
|
||||
|
||||
def test_simple_success(self):
|
||||
self.swift.head_object.return_value = {
|
||||
'content-length': 1
|
||||
}
|
||||
self.swift.get_container.return_value = (
|
||||
{}, []
|
||||
)
|
||||
data = 'data'
|
||||
action = logging_to_swift.PublishUILogToSwiftAction(
|
||||
data, self.container)
|
||||
action.run(self.ctx)
|
||||
|
||||
self.swift.get_object.assert_called_once()
|
||||
self.swift.head_object.assert_called_once()
|
||||
self.swift.put_object.assert_called_once()
|
||||
self.swift.get_container.assert_called_once()
|
||||
|
||||
def test_rotate(self):
|
||||
self.swift.head_object.return_value = {
|
||||
'content-length': 2e7
|
||||
}
|
||||
self.swift.get_container.return_value = (
|
||||
{}, []
|
||||
)
|
||||
|
||||
old_data = 'old data'
|
||||
new_data = 'new data'
|
||||
result = old_data + '\n' + new_data
|
||||
|
||||
self.swift.get_object.return_value = ({}, old_data)
|
||||
|
||||
action = logging_to_swift.PublishUILogToSwiftAction(
|
||||
new_data, self.container)
|
||||
action.run(self.ctx)
|
||||
|
||||
self.swift.head_object.assert_called_once()
|
||||
self.swift.put_object.assert_called_with(
|
||||
self.container,
|
||||
'tripleo-ui.logs',
|
||||
result
|
||||
)
|
||||
|
||||
|
||||
class PrepareLogDownloadActionTest(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(PrepareLogDownloadActionTest, self).setUp()
|
||||
self.log_files = (
|
||||
'tripleo-ui.logs.2',
|
||||
'tripleo-ui.logs.1',
|
||||
'tripleo-ui.logs'
|
||||
)
|
||||
self.swift = mock.MagicMock()
|
||||
self.swift.get_container.return_value = (
|
||||
{'x-container-meta-usage-tripleo': 'plan'}, [
|
||||
{'name': lf} for lf in self.log_files
|
||||
]
|
||||
)
|
||||
self.swift.get_object.return_value = ({}, 'log content')
|
||||
swift_patcher = mock.patch(
|
||||
'tripleo_common.actions.base.TripleOAction.get_object_client',
|
||||
return_value=self.swift)
|
||||
swift_patcher.start()
|
||||
self.addCleanup(swift_patcher.stop)
|
||||
|
||||
self.ctx = mock.MagicMock()
|
||||
|
||||
@mock.patch('tripleo_common.utils.tarball.create_tarball')
|
||||
@mock.patch('tempfile.mkdtemp')
|
||||
def test_run_success(self, mock_mkdtemp, mock_create_tarball):
|
||||
get_object_mock_calls = [
|
||||
mock.call('logging-container', lf) for lf in self.log_files
|
||||
]
|
||||
get_container_mock_calls = [
|
||||
mock.call('logging-container')
|
||||
]
|
||||
mock_mkdtemp.return_value = '/tmp/test123'
|
||||
|
||||
action = logging_to_swift.PrepareLogDownloadAction(
|
||||
'logging-container', 'downloads-container', 3600
|
||||
)
|
||||
|
||||
action.run(self.ctx)
|
||||
|
||||
self.swift.get_container.assert_has_calls(get_container_mock_calls)
|
||||
self.swift.get_object.assert_has_calls(
|
||||
get_object_mock_calls, any_order=True)
|
||||
mock_create_tarball.assert_called_once()
|
||||
|
||||
@mock.patch('tripleo_common.utils.tarball.create_tarball')
|
||||
def test_run_error_creating_tarball(self, mock_create_tarball):
|
||||
mock_create_tarball.side_effect = processutils.ProcessExecutionError
|
||||
|
||||
action = logging_to_swift.PrepareLogDownloadAction(
|
||||
'logging-container', 'downloads-container', 3600
|
||||
)
|
||||
|
||||
result = action.run(self.ctx)
|
||||
|
||||
error = "Error while creating a tarball"
|
||||
self.assertIn(error, result.error)
|
@ -15,6 +15,8 @@
|
||||
|
||||
import mock
|
||||
|
||||
from swiftclient import exceptions as swiftexceptions
|
||||
|
||||
from tripleo_common.tests import base
|
||||
from tripleo_common.utils import swift as swift_utils
|
||||
|
||||
@ -75,3 +77,13 @@ class SwiftTest(base.TestCase):
|
||||
self.swiftclient.get_account.assert_called()
|
||||
self.swiftclient.get_container.assert_called()
|
||||
self.swiftclient.delete_object.assert_not_called()
|
||||
|
||||
def test_get_or_create_container_create(self):
|
||||
self.swiftclient.get_container.side_effect = \
|
||||
swiftexceptions.ClientException('error')
|
||||
swift_utils.get_or_create_container(self.swiftclient, 'abc')
|
||||
self.swiftclient.put_container.assert_called()
|
||||
|
||||
def test_get_or_create_container_get(self):
|
||||
swift_utils.get_or_create_container(self.swiftclient, 'abc')
|
||||
self.swiftclient.put_container.assert_not_called()
|
||||
|
83
tripleo_common/tests/utils/test_time_functions.py
Normal file
83
tripleo_common/tests/utils/test_time_functions.py
Normal file
@ -0,0 +1,83 @@
|
||||
# Copyright (c) 2017 Red Hat, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import contextlib
|
||||
import datetime
|
||||
|
||||
from tripleo_common.tests import base
|
||||
from tripleo_common.utils import time_functions
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def mock_now(dt):
|
||||
"""Context manager for mocking out datetime.utcnow() in unit tests.
|
||||
|
||||
Example:
|
||||
|
||||
with mock_now(datetime.datetime(2011, 2, 3, 10, 11)):
|
||||
assert datetime.datetime.utcnow() \
|
||||
== datetime.datetime(2011, 2, 3, 10, 11)
|
||||
"""
|
||||
class MockDatetime(datetime.datetime):
|
||||
|
||||
@classmethod
|
||||
def utcnow(cls):
|
||||
return dt
|
||||
|
||||
real_datetime = datetime.datetime
|
||||
datetime.datetime = MockDatetime
|
||||
|
||||
try:
|
||||
yield datetime.datetime
|
||||
finally:
|
||||
datetime.datetime = real_datetime
|
||||
|
||||
|
||||
class TimeFunctionsTest(base.TestCase):
|
||||
|
||||
def test_timestamp(self):
|
||||
fake_date = datetime.datetime(2017, 7, 31, 13, 0, 0)
|
||||
with mock_now(fake_date):
|
||||
self.assertEqual(time_functions.timestamp(), '20170731-130000')
|
||||
|
||||
def test_epoch_formatting(self):
|
||||
self.assertEqual(
|
||||
time_functions.epoch_to_formatted_date(1000),
|
||||
'1970-01-01 00:00:01')
|
||||
|
||||
self.assertEqual(
|
||||
time_functions.epoch_to_formatted_date(1000 * 60),
|
||||
'1970-01-01 00:01:00')
|
||||
|
||||
self.assertEqual(
|
||||
time_functions.epoch_to_formatted_date(1000 * 60 * 60 * 24),
|
||||
'1970-01-02 00:00:00')
|
||||
|
||||
self.assertEqual(
|
||||
time_functions.epoch_to_formatted_date(1000.0),
|
||||
'1970-01-01 00:00:01')
|
||||
|
||||
self.assertEqual(
|
||||
time_functions.epoch_to_formatted_date('1000'),
|
||||
'1970-01-01 00:00:01')
|
||||
|
||||
self.assertRaises(
|
||||
ValueError,
|
||||
time_functions.epoch_to_formatted_date,
|
||||
'abc')
|
||||
|
||||
self.assertRaises(
|
||||
TypeError,
|
||||
time_functions.epoch_to_formatted_date,
|
||||
None)
|
@ -14,7 +14,16 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from swiftclient import exceptions as swiftexceptions
|
||||
|
||||
from tripleo_common import constants
|
||||
from tripleo_common.utils import tarball
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def empty_container(swiftclient, name):
|
||||
@ -43,3 +52,45 @@ def empty_container(swiftclient, name):
|
||||
def delete_container(swiftclient, name):
|
||||
empty_container(swiftclient, name)
|
||||
swiftclient.delete_container(name)
|
||||
|
||||
|
||||
def download_container(swiftclient, container, dest):
|
||||
"""Download the contents of a Swift container to a directory"""
|
||||
|
||||
objects = swiftclient.get_container(container)[1]
|
||||
|
||||
for obj in objects:
|
||||
filename = obj['name']
|
||||
contents = swiftclient.get_object(container, filename)[1]
|
||||
path = os.path.join(dest, filename)
|
||||
dirname = os.path.dirname(path)
|
||||
|
||||
if not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
|
||||
with open(path, 'w') as f:
|
||||
f.write(contents)
|
||||
|
||||
|
||||
def get_or_create_container(swiftclient, container):
|
||||
try:
|
||||
return swiftclient.get_container(container)
|
||||
except swiftexceptions.ClientException:
|
||||
LOG.debug("Container %s doesn't exist, creating...", container)
|
||||
return swiftclient.put_container(container)
|
||||
|
||||
|
||||
def create_and_upload_tarball(swiftclient,
|
||||
tmp_dir,
|
||||
container,
|
||||
tarball_name,
|
||||
delete_after=3600):
|
||||
"""Create a tarball containing the tmp_dir and upload it to Swift."""
|
||||
headers = {'X-Delete-After': delete_after}
|
||||
|
||||
get_or_create_container(swiftclient, container)
|
||||
|
||||
with tempfile.NamedTemporaryFile() as tmp_tarball:
|
||||
tarball.create_tarball(tmp_dir, tmp_tarball.name)
|
||||
swiftclient.put_object(container, tarball_name, tmp_tarball,
|
||||
headers=headers)
|
||||
|
29
tripleo_common/utils/time_functions.py
Normal file
29
tripleo_common/utils/time_functions.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Copyright 2017 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
# implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
|
||||
|
||||
def timestamp():
|
||||
"""Return a UTC-now timestamp as a string"""
|
||||
return datetime.datetime.utcnow().strftime('%Y%m%d-%H%M%S')
|
||||
|
||||
|
||||
def epoch_to_formatted_date(epoch):
|
||||
"""Convert an epoch time to a string"""
|
||||
epoch = float(epoch) / 1000
|
||||
dt = datetime.datetime.utcfromtimestamp(epoch)
|
||||
return dt.strftime('%Y-%m-%d %H:%M:%S')
|
@ -462,3 +462,140 @@ workflows:
|
||||
deprecated: <% $.get('formatted', []) %>
|
||||
on-success:
|
||||
- fail: <% $.get('status') = "FAILED" %>
|
||||
|
||||
publish_ui_logs_to_swift:
|
||||
description: >
|
||||
This workflow drains a zaqar queue, and publish its messages into a log
|
||||
file in swift. This workflow is called by cron trigger.
|
||||
|
||||
input:
|
||||
- queue_name: tripleo
|
||||
- logging_queue_name: tripleo-ui-logging
|
||||
- logging_container: tripleo-ui-logs
|
||||
|
||||
tasks:
|
||||
|
||||
get_messages:
|
||||
action: zaqar.queue_messages
|
||||
on-success: format_messages
|
||||
on-error: get_messages_set_status_failed
|
||||
input:
|
||||
queue_name: <% $.logging_queue_name %>
|
||||
|
||||
publish:
|
||||
messages: <% task(get_messages).result %>
|
||||
|
||||
format_messages:
|
||||
action: tripleo.logging_to_swift.format_messages
|
||||
on-success: upload_to_swift
|
||||
input:
|
||||
messages: <% $.messages %>
|
||||
|
||||
publish:
|
||||
messages: <% task(format_messages).result %>
|
||||
|
||||
upload_to_swift:
|
||||
action: tripleo.logging_to_swift.publish_ui_log_to_swift
|
||||
on-success: set_status_success
|
||||
on-error: upload_to_swift_set_status_failed
|
||||
input:
|
||||
logging_data: <% $.messages %>
|
||||
logging_container: <% $.logging_container %>
|
||||
|
||||
set_status_success:
|
||||
on-success: notify_zaqar
|
||||
publish:
|
||||
status: SUCCESS
|
||||
message: <% task(upload_to_swift).result %>
|
||||
|
||||
upload_to_swift_set_status_failed:
|
||||
on-success: notify_zaqar
|
||||
publish:
|
||||
status: FAILED
|
||||
message: <% task(upload_to_swift).result %>
|
||||
|
||||
get_messages_set_status_failed:
|
||||
on-success: notify_zaqar
|
||||
publish:
|
||||
status: FAILED
|
||||
message: <% task(get_messages).result %>
|
||||
|
||||
notify_zaqar:
|
||||
action: zaqar.queue_post
|
||||
input:
|
||||
queue_name: <% $.queue_name %>
|
||||
messages:
|
||||
body:
|
||||
type: tripleo.plan_management.v1.publish_ui_logs_to_swift
|
||||
payload:
|
||||
status: <% $.status %>
|
||||
message: <% $.get('message', '') %>
|
||||
execution: <% execution() %>
|
||||
on-success:
|
||||
- fail: <% $.get('status') = "FAILED" %>
|
||||
|
||||
download_logs:
|
||||
description: Creates a tarball with logging data
|
||||
input:
|
||||
- queue_name: tripleo
|
||||
- logging_container: "tripleo-ui-logs"
|
||||
- downloads_container: "tripleo-ui-logs-downloads"
|
||||
- delete_after: 3600
|
||||
|
||||
tasks:
|
||||
|
||||
prepare_log_download:
|
||||
action: tripleo.logging_to_swift.prepare_log_download
|
||||
input:
|
||||
logging_container: <% $.logging_container %>
|
||||
downloads_container: <% $.downloads_container %>
|
||||
delete_after: <% $.delete_after %>
|
||||
on-success: create_tempurl
|
||||
on-error: download_logs_set_status_failed
|
||||
publish:
|
||||
filename: <% task(prepare_log_download).result %>
|
||||
|
||||
create_tempurl:
|
||||
action: tripleo.swift.tempurl
|
||||
on-success: set_status_success
|
||||
on-error: create_tempurl_set_status_failed
|
||||
input:
|
||||
container: <% $.downloads_container %>
|
||||
obj: <% $.filename %>
|
||||
valid: 3600
|
||||
publish:
|
||||
tempurl: <% task(create_tempurl).result %>
|
||||
|
||||
set_status_success:
|
||||
on-success: notify_zaqar
|
||||
publish:
|
||||
status: SUCCESS
|
||||
message: <% task(create_tempurl).result %>
|
||||
tempurl: <% task(create_tempurl).result %>
|
||||
|
||||
download_logs_set_status_failed:
|
||||
on-success: notify_zaqar
|
||||
publish:
|
||||
status: FAILED
|
||||
message: <% task(prepare_log_download).result %>
|
||||
|
||||
create_tempurl_set_status_failed:
|
||||
on-success: notify_zaqar
|
||||
publish:
|
||||
status: FAILED
|
||||
message: <% task(create_tempurl).result %>
|
||||
|
||||
notify_zaqar:
|
||||
action: zaqar.queue_post
|
||||
input:
|
||||
queue_name: <% $.queue_name %>
|
||||
messages:
|
||||
body:
|
||||
type: tripleo.plan_management.v1.download_logs
|
||||
payload:
|
||||
status: <% $.status %>
|
||||
message: <% $.get('message', '') %>
|
||||
execution: <% execution() %>
|
||||
tempurl: <% $.get('tempurl', '') %>
|
||||
on-success:
|
||||
- fail: <% $.get('status') = "FAILED" %>
|
||||
|
Loading…
Reference in New Issue
Block a user