summaryrefslogtreecommitdiffstats
path: root/ipatests/test_integration/host.py
diff options
context:
space:
mode:
authorPetr Viktorin <pviktori@redhat.com>2013-05-28 13:31:37 +0200
committerPetr Viktorin <pviktori@redhat.com>2013-07-15 15:49:06 +0200
commit353f3c62c3dc95db471a2b23fcd90d6071542362 (patch)
tree00fd09352061194c964cd151b1480b6405b69a6d /ipatests/test_integration/host.py
parentc577420e40a353f3038263bf8ef763f7c01f6f22 (diff)
downloadfreeipa-353f3c62c3dc95db471a2b23fcd90d6071542362.tar.gz
freeipa-353f3c62c3dc95db471a2b23fcd90d6071542362.tar.xz
freeipa-353f3c62c3dc95db471a2b23fcd90d6071542362.zip
Add a framework for integration testing
Add methods to run commands and copy files to Host objects. Adds a base class for integration tests which can currently install and uninstall IPA in a "star" topology with per-test specified number of hosts. A simple test for user replication between two masters is provided. Log files from the remote hosts can be marked for collection, but the actual collection is left to a Nose plugin. Part of the work for: https://fedorahosted.org/freeipa/ticket/3621
Diffstat (limited to 'ipatests/test_integration/host.py')
-rw-r--r--ipatests/test_integration/host.py183
1 files changed, 183 insertions, 0 deletions
diff --git a/ipatests/test_integration/host.py b/ipatests/test_integration/host.py
new file mode 100644
index 000000000..a663a9906
--- /dev/null
+++ b/ipatests/test_integration/host.py
@@ -0,0 +1,183 @@
+# Authors:
+# Petr Viktorin <pviktori@redhat.com>
+#
+# Copyright (C) 2013 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""Host class for integration testing"""
+
+import os
+import collections
+import socket
+
+import paramiko
+
+from ipapython import ipautil
+from ipapython.ipa_log_manager import log_mgr
+
+RunResult = collections.namedtuple('RunResult', 'output exit_code')
+
+
+class Host(object):
+ """Configuration for an IPA host"""
+ def __init__(self, domain, hostname, role, index):
+ self.log = log_mgr.get_logger(self)
+ self.domain = domain
+ self.role = role
+ self.index = index
+
+ shortname, dot, ext_domain = hostname.partition('.')
+ self.hostname = shortname + '.' + self.domain.name
+ self.external_hostname = hostname
+
+ if self.config.ipv6:
+ # $(dig +short $M $rrtype|tail -1)
+ stdout, stderr, returncode = ipautil.run(
+ ['dig', '+short', self.external_hostname, 'AAAA'])
+ self.ip = stdout.splitlines()[-1].strip()
+ else:
+ try:
+ self.ip = socket.gethostbyname(self.external_hostname)
+ except socket.gaierror:
+ self.ip = None
+
+ if not self.ip:
+ self.ip = ''
+ self.role = 'other'
+
+ self.root_password = self.config.root_password
+ self.host_key = None
+ self.ssh_port = 22
+
+ self.env_sh_path = os.path.join(domain.config.test_dir, 'env.sh')
+
+ self.log = log_mgr.get_logger('%s.%s.%s' % (
+ self.__module__, type(self).__name__, self.hostname))
+
+ def __repr__(self):
+ template = ('<{s.__module__}.{s.__class__.__name__} '
+ '{s.hostname} ({s.role})>')
+ return template.format(s=self)
+
+ @classmethod
+ def from_env(cls, env, domain, hostname, role, index):
+ self = cls(domain, hostname, role, index)
+ return self
+
+ @property
+ def config(self):
+ return self.domain.config
+
+ def to_env(self, **kwargs):
+ """Return environment variables specific to this host"""
+ env = self.domain.to_env(**kwargs)
+
+ role = self.role.upper()
+ if self.role != 'master':
+ role += str(self.index)
+
+ env['MYHOSTNAME'] = self.hostname
+ env['MYBEAKERHOSTNAME'] = self.external_hostname
+ env['MYIP'] = self.ip
+
+ env['MYROLE'] = '%s%s' % (role, self.domain._env)
+ env['MYENV'] = str(self.domain.index)
+
+ return env
+
+ def run_command(self, argv, set_env=True, stdin_text=None,
+ ignore_stdout=False):
+ assert argv
+ self.log.info('RUN %s', argv)
+ ssh = self.transport.open_channel('session')
+ try:
+ ssh.invoke_shell()
+ ssh.set_combine_stderr(True)
+ stdin = ssh.makefile('wb')
+ stdout = ssh.makefile('rb')
+
+ if set_env:
+ stdin.write('. %s\n' % self.env_sh_path)
+ stdin.write('set -ex\n')
+
+ for arg in argv:
+ stdin.write(ipautil.shell_quote(arg))
+ stdin.write(' ')
+ if stdin_text:
+ stdin_filename = os.path.join(self.config.test_dir, 'stdin')
+ with self.sftp.open(stdin_filename, 'w') as f:
+ f.write(stdin_text)
+ stdin.write('<')
+ stdin.write(stdin_filename)
+ else:
+ stdin.write('< /dev/null')
+ if ignore_stdout:
+ stdin.write('> /dev/null')
+ stdin.write('\n')
+ ssh.shutdown_write()
+ output = []
+ for line in stdout:
+ output.append(line)
+ self.log.info(' %s', line.strip('\n'))
+ exit_status = ssh.recv_exit_status()
+ self.log.info(' -> Exit code %s', exit_status)
+ if exit_status:
+ raise RuntimeError('Command %s exited with error code %s' % (
+ argv[0], exit_status))
+ return RunResult(''.join(output), exit_status)
+ finally:
+ ssh.close()
+
+ @property
+ def transport(self):
+ """Paramiko Transport connected to this host"""
+ try:
+ return self._transport
+ except AttributeError:
+ sock = socket.create_connection((self.hostname, self.ssh_port))
+ self._transport = transport = paramiko.Transport(sock)
+ transport.connect(hostkey=self.host_key, username='root',
+ password=self.root_password)
+ return transport
+
+ @property
+ def sftp(self):
+ """Paramiko SFTPClient connected to this host"""
+ try:
+ return self._sftp
+ except AttributeError:
+ transport = self.transport
+ self._sftp = paramiko.SFTPClient.from_transport(transport)
+ return self._sftp
+
+ def mkdir_recursive(self, path):
+ """`mkdir -p` on the remote host"""
+ try:
+ self.sftp.chdir(path)
+ except IOError:
+ self.mkdir_recursive(os.path.dirname(path))
+ self.sftp.mkdir(path)
+ self.sftp.chdir(path)
+
+ def get_file_contents(self, filename):
+ self.log.info('READ %s', filename)
+ with self.sftp.open(filename) as f:
+ return f.read()
+
+ def put_file_contents(self, filename, contents):
+ self.log.info('WRITE %s', filename)
+ with self.sftp.open(filename, 'w') as f:
+ return f.write(contents)