--- /dev/null
+import logging
+import os
+
+from teuthology.orchestra.cluster import Cluster
+
+from . import Task
+
+log = logging.getLogger(__name__)
+
+
+class ConsoleLog(Task):
+ enabled = True
+ name = 'console_log'
+
+ def __init__(self, ctx=None, config=None):
+ super(ConsoleLog, self).__init__(ctx, config)
+ if self.config.get('enabled') is False:
+ self.enabled = False
+ if not getattr(self.ctx, 'archive', None):
+ self.enabled = False
+
+ def filter_hosts(self):
+ super(ConsoleLog, self).filter_hosts()
+ if not hasattr(self.ctx, 'cluster'):
+ return
+ new_cluster = Cluster()
+ for (remote, roles) in self.cluster.remotes.iteritems():
+ if not hasattr(remote.console, 'spawn_sol_log'):
+ log.debug("%s does not support IPMI; excluding",
+ remote.shortname)
+ elif not remote.console.has_credentials:
+ log.debug("IPMI credentials not found for %s; excluding",
+ remote.shortname)
+ else:
+ new_cluster.add(remote, roles)
+ self.cluster = new_cluster
+ return self.cluster
+
+ def setup(self):
+ if not self.enabled:
+ return
+ super(ConsoleLog, self).setup()
+ self.processes = list()
+ self.setup_archive()
+
+ def setup_archive(self):
+ self.archive_dir = os.path.join(
+ self.ctx.archive,
+ 'console_logs',
+ )
+ os.makedirs(self.archive_dir)
+
+ def begin(self):
+ if not self.enabled:
+ return
+ super(ConsoleLog, self).begin()
+ self.start_logging()
+
+ def start_logging(self):
+ for remote in self.cluster.remotes.keys():
+ log_path = os.path.join(
+ self.archive_dir,
+ "%s.log" % remote.shortname,
+ )
+ proc = remote.console.spawn_sol_log(log_path)
+ self.processes.append(proc)
+
+ def end(self):
+ if not self.enabled:
+ return
+ super(ConsoleLog, self).end()
+ self.stop_logging()
+
+ def stop_logging(self, force=False):
+ for proc in self.processes:
+ if proc.poll() is not None:
+ continue
+ if force:
+ proc.kill()
+ else:
+ proc.terminate()
+
+ def teardown(self):
+ if not self.enabled:
+ return
+ self.stop_logging(force=True)
+ super(ConsoleLog, self).teardown()
+
+
+task = ConsoleLog
--- /dev/null
+import os
+
+from mock import patch
+
+from teuthology.config import FakeNamespace
+from teuthology.config import config as teuth_config
+from teuthology.orchestra.cluster import Cluster
+from teuthology.orchestra.remote import Remote
+from teuthology.task.console_log import ConsoleLog
+
+from . import TestTask
+
+
+class TestConsoleLog(TestTask):
+ klass = ConsoleLog
+ task_name = 'console_log'
+
+ def setup(self):
+ teuth_config.ipmi_domain = 'ipmi.domain'
+ teuth_config.ipmi_user = 'ipmi_user'
+ teuth_config.ipmi_password = 'ipmi_pass'
+ self.ctx = FakeNamespace()
+ self.ctx.cluster = Cluster()
+ self.ctx.cluster.add(Remote('user@remote1'), ['role1'])
+ self.ctx.cluster.add(Remote('user@remote2'), ['role2'])
+ self.ctx.config = dict()
+ self.ctx.archive = '/fake/path'
+ self.task_config = dict()
+ self.start_patchers()
+
+ def start_patchers(self):
+ self.patchers = list()
+ self.patchers.append(patch(
+ 'teuthology.task.console_log.os.makedirs',
+ ))
+ for patcher in self.patchers:
+ patcher.start()
+
+ def teardown(self):
+ for patcher in self.patchers:
+ patcher.stop()
+
+ def test_enabled(self):
+ task = self.klass(self.ctx, self.task_config)
+ assert task.enabled is True
+
+ def test_disabled_noarchive(self):
+ self.ctx.archive = None
+ task = self.klass(self.ctx, self.task_config)
+ assert task.enabled is False
+
+ def test_has_credentials(self):
+ for remote in self.ctx.cluster.remotes.keys():
+ remote.console.has_credentials = False
+ task = self.klass(self.ctx, self.task_config)
+ assert len(task.cluster.remotes.keys()) == 0
+
+ def test_remotes(self):
+ with self.klass(self.ctx, self.task_config) as task:
+ assert len(task.cluster.remotes) == len(self.ctx.cluster.remotes)
+
+ @patch('teuthology.orchestra.console.PhysicalConsole')
+ def test_begin(self, m_pconsole):
+ with self.klass(self.ctx, self.task_config) as task:
+ assert len(task.processes) == len(self.ctx.cluster.remotes)
+ for remote in task.cluster.remotes.keys():
+ dest_path = os.path.join(
+ self.ctx.archive, '%s.log' % remote.shortname)
+ assert remote.console.spawn_sol_log.called_once_with(
+ dest_path=dest_path)
+
+ @patch('teuthology.orchestra.console.PhysicalConsole')
+ def test_end(self, m_pconsole):
+ with self.klass(self.ctx, self.task_config) as task:
+ pass
+ for proc in task.processes:
+ assert proc.terminate.called_once_with()
+ assert proc.kill.called_once_with()