# Copyright(c) 2016, Oracle and/or its affiliates. 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 logging
import os
import tarfile
import tempfile
from kollacli.api.exceptions import FailedOperation
from kollacli.common.inventory import Inventory
from kollacli.common.properties import AnsibleProperties
from kollacli.common.utils import get_kolla_home
from kollacli.common.utils import get_kolla_log_dir
from kollacli.common.utils import get_kollacli_etc
from kollacli.common.utils import run_cmd
LOG = logging.getLogger(__name__)
[docs]class HostLogs(object):
def __init__(self, hostname, inventory, servicenames):
self.hostname = hostname
self.inventory = inventory
self.servicenames = servicenames
self.container_info = {} # container_id: container_img_name
self.filtered_servicenames = {}
[docs] def load_container_info(self):
"""get the list of containers on the host"""
hostname = self.hostname
err_msg, output = \
self.inventory.run_ansible_command('-a "docker ps -a"', hostname)
if err_msg:
msg = 'Error accessing host %s : %s %s' % (hostname, err_msg,
output)
raise FailedOperation(msg)
if not output:
msg = ('Host %s is not accessible.' % hostname)
raise FailedOperation(msg)
else:
if '>>' not in output:
msg = ('Host: %s. Invalid ansible return data: [%s].'
% (hostname, output))
raise FailedOperation(msg)
if 'NAMES' not in output:
msg = ('Host: %s. Invalid docker ps return data: [%s].'
% (hostname, output))
raise FailedOperation(msg)
ansible_properties = AnsibleProperties()
base_distro = \
ansible_properties.get_property('kolla_base_distro')
install_type = \
ansible_properties.get_property('kolla_install_type')
# typically this prefix will be "ol-openstack-"
container_prefix = base_distro + '-' + install_type + '-'
# process ps output
containers = {}
# the ps output is after the '>>'
output = output.split('>>', 1)[1]
LOG.info('docker ps -a on host: %s:\n%s' % (hostname, output))
lines = output.split('\n')
for line in lines:
tokens = line.split()
if len(tokens) < 2:
continue
cid = tokens[0]
image = tokens[1]
if container_prefix not in image:
# skip non-kolla containers
continue
name = image.split(container_prefix)[1]
name = name.split(':')[0]
containers[cid] = name
self.container_info = containers
[docs] def get_log(self, container_id):
"""read the container log"""
hostname = self.hostname
cmd = '-a "docker logs %s"' % container_id
err_msg, output = self.inventory.run_ansible_command(cmd, hostname)
if err_msg:
msg = 'Error accessing host %s : %s ' % (hostname, err_msg)
raise FailedOperation(msg)
if not output:
msg = ('Host %s is not accessible.' % hostname)
raise FailedOperation(msg)
if '>>' not in output:
msg = ('Host: %s. Invalid ansible return data: [%s].'
% (hostname, output))
raise FailedOperation(msg)
# the log info is after the '>>'
output = output.split('>>', 1)[1]
return output
[docs] def write_logs(self, dirname):
"""write out the log files for all containers"""
for container_id, container_name in self.filtered_services.items():
logdata = self.get_log(container_id)
if logdata:
logname = '%s_%s.log' % (container_name, container_id)
self.write_logfile(dirname, logname, logdata)
else:
LOG.warn('No log data found for service %s on host %s'
% (container_name, self.hostname))
[docs] def write_logfile(self, dirpath, logname, logdata):
"""write out one log file"""
hostdir = os.path.join(dirpath, self.hostname)
if not os.path.exists(hostdir):
os.mkdir(hostdir)
fpath = os.path.join(hostdir, logname)
with open(fpath, 'w') as logfile:
logfile.write(logdata)
[docs] def filter_services(self):
"""filter services to only those of interest"""
services_subset = {}
for host_svcid, host_svcname in self.container_info.items():
for servicename in self.servicenames:
if (host_svcname == servicename or
host_svcname.startswith(servicename + '-')):
services_subset[host_svcid] = host_svcname
self.filtered_services = services_subset
[docs]def get_logs(servicenames, hostname, dirname):
inventory = Inventory.load()
inventory.validate_hostnames([hostname])
inventory.validate_servicenames(servicenames)
logs = HostLogs(hostname, inventory, servicenames)
logs.load_container_info()
logs.filter_services()
logs.write_logs(dirname)
[docs]def dump(dirpath):
"""Dumps configuration data for debugging
Dumps most files in /etc/kolla and /usr/share/kolla into a
tar file so be given to support / development to help with
debugging problems.
"""
kolla_home = get_kolla_home()
kolla_logs = get_kolla_log_dir()
kolla_ansible = os.path.join(kolla_home, 'ansible')
kollacli_etc = get_kollacli_etc().rstrip('/')
ketc = 'kolla/etc/'
kshare = 'kolla/share/'
fd, dump_path = tempfile.mkstemp(dir=dirpath, prefix='kollacli_dump_',
suffix='.tgz')
os.close(fd) # avoid fd leak
with tarfile.open(dump_path, 'w:gz') as tar:
# Can't blanket add kolla_home because the .ssh dir is
# accessible by the kolla user only (not kolla group)
tar.add(kolla_ansible,
arcname=kshare + os.path.basename(kolla_ansible))
# Can't blanket add kolla_etc because the passwords.yml
# file is accessible by the kolla user only (not kolla group)
tar.add(kollacli_etc,
arcname=ketc + os.path.basename(kollacli_etc))
# add kolla log files
if os.path.isdir(kolla_logs):
tar.add(kolla_logs)
# add output of various commands
_add_cmd_info(tar)
return dump_path
def _add_cmd_info(tar):
# run all the kollacli list commands
cmds = ['kollacli --version',
'kollacli service listgroups',
'kollacli service list',
'kollacli group listservices',
'kollacli group listhosts',
'kollacli host list',
'kollacli property list',
'kollacli password list']
# collect the json inventory output
inventory = Inventory.load()
inv_path = inventory.create_json_gen_file()
cmds.append(inv_path)
try:
fd, path = tempfile.mkstemp(suffix='.tmp')
os.close(fd)
with open(path, 'w') as tmp_file:
for cmd in cmds:
err_msg, output = run_cmd(cmd, False)
tmp_file.write('\n\n$ %s\n' % cmd)
if err_msg:
tmp_file.write('Error message: %s\n' % err_msg)
for line in output:
tmp_file.write(line + '\n')
tar.add(path, arcname=os.path.join('kolla', 'cmds_output'))
except Exception as e:
raise e
finally:
if path:
os.remove(path)
inventory.remove_json_gen_file(inv_path)
return