On 12/04/2015 12:49 PM, Jan Cholasta wrote:
> On 4.12.2015 12:43, Petr Viktorin wrote:
>> On 12/04/2015 12:15 PM, Jan Cholasta wrote:
>>> On 4.12.2015 10:53, Petr Viktorin wrote:
>>>> On 12/04/2015 08:51 AM, Jan Cholasta wrote:
>>>>> On 3.12.2015 15:42, Petr Viktorin wrote:
>>>>>> On 12/03/2015 10:55 AM, Jan Cholasta wrote:
>> [...]
>>>>>>> If we do encode/decode in run(), I think there should be a way to
>>>>>>> override the default encoding. My idea was to either accept/return
>>>>>>> only
>>>>>>> bytes and let the caller handle encoding themselves, or to handle
>>>>>>> encoding in run(), but be able to override the defaults.
>>>>>>>
>>>>>>> Given we handle encoding in run(), I would imagine it like this:
>>>>>>>
>>>>>>>        def run(args, stdin=None, raiseonerr=True, nolog=(),
>>>>>>> env=None,
>>>>>>>                capture_stdout=False, skip_output=False, cwd=None,
>>>>>>>                runas=None, timeout=None, suplementary_groups=[],
>>>>>>>                capture_stderr=False, encode_stdout=False,
>>>>>>>                encode_stderr=False, encoding=None):
>>>>>>>
>>>>>>> i.e. nothing is captured by default, when stdout or stderr are
>>>>>>> captured
>>>>>>> they are returned as bytes by default, when stdout or stderr are
>>>>>>> returned as text they are decoded using the locale encoding by
>>>>>>> default,
>>>>>>> or the encoding can be explicitly specified.
>>>>>>>
>>>>>>> Do you think this is feasible?
>>>>>>
>>>>>> Feasible, yes.
>>>>>> In the majority of cases where the output is needed, we want text
>>>>>> encoded with locale.getpreferredencoding(), so I'd rather make
>>>>>> that the
>>>>>> default rather than bytes.
>>>>>>
>>>>>> Or we could make "encode_stdout" imply "capture_stdout", so you
>>>>>> wouldn't
>>>>>> have to always specify both. (It would be better named
>>>>>> "encoded_stdout"
>>>>>> in that case.)
>>>>>
>>>>> I think it should be OK to decode by default. (IMO it would be best to
>>>>> reverse the logic and name it "raw_stdout", with False default. Do we
>>>>> need "raw_stderr" at all? I don't think we do.)
>>>>
>>>> Actually, now that I think about it: if the result was a namedtuple
>>>> subclass, we can always set extra raw_stdout/raw_stderr attributes on
>>>> it. (Unless skip_output=True, of course.)
>>>>
>>>>      result = run(['generate_binary_data'])
>>>>      use(result.raw_stdout)
>>>>
>>>>      # and for backcompat,
>>>>      rc, _out, _err = result
>>>
>>> Good idea.
>>>
>>> Perhaps we should use the same names as CalledProcessError for the
>>> attributes, i.e. "returncode", "output", and "error_output",
>>> "raw_output", "raw_error_output" for the new attributes?
> 
> (Actually, since "returncode" is not "return_code", it should probably
> be "returncode", "output", "erroroutput", "raw_output", "raw_erroroutput".)
> 
>>
>> OK. It's also good to use different names than Popen's stdout/stderr,
>> which are file-like objects rather than strings. But then,
>> capture_stdout/capture_stderr should be
>> capture_output/capture_error_output.

Here is the new patch.
I also added output_log and error_log to the result to abstract the
following common pattern:

    result = run([...])
    if result.returncode != 0:
        stderr = result.error_output
        if six.PY3:
            error = stderr.encode(locale.getpreferredencoding(), 'replace')
        self.log.critical('...: %s', stderr)


-- 
Petr Viktorin

From 5351a375670be5c1cad756de21428a13501077ab Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pvikt...@redhat.com>
Date: Wed, 25 Nov 2015 17:17:18 +0100
Subject: [PATCH] Refactor ipautil.run

The ipautil.run function now returns an object with returncode and
output are accessible as attributes.

The stdout and stderr of all commands are logged (unless skip_output is given).

The stdout/stderr contents must be explicitly requested with a keyword
argument, otherwise they are None.
This is because in Python 3, the output needs to be decoded, and that can
fail if it's not decodable (human-readable) text.

The raw (bytes) output is always available from the result object,
as is "leniently" decoded output suitable for logging.

All calls are changed to reflect this.

A use of Popen in cainstance is changed to ipautil.run.
---
 .../certmonger/dogtag-ipa-ca-renew-agent-submit    |  14 ++-
 install/certmonger/ipa-server-guard                |  16 ++-
 install/oddjob/com.redhat.idm.trust-fetch-domains  |   5 +-
 install/tools/ipa-replica-conncheck                |  37 +++---
 ipa-client/ipa-install/ipa-client-install          |  43 ++++---
 ipalib/plugins/pwpolicy.py                         |   6 +-
 ipaplatform/base/services.py                       |  41 +++---
 ipaplatform/redhat/services.py                     |   4 +-
 ipaplatform/redhat/tasks.py                        |   7 +-
 ipapython/certdb.py                                |  27 ++--
 ipapython/dnssec/bindmgr.py                        |   7 +-
 ipapython/dnssec/odsmgr.py                         |   3 +-
 ipapython/ipautil.py                               | 140 +++++++++++++++++----
 ipapython/kernel_keyring.py                        |  58 +++++----
 ipaserver/dcerpc.py                                |  16 +--
 ipaserver/install/cainstance.py                    |  49 ++++----
 ipaserver/install/certs.py                         |  17 +--
 ipaserver/install/httpinstance.py                  |   9 +-
 ipaserver/install/ipa_backup.py                    |  53 ++++----
 ipaserver/install/ipa_restore.py                   |  38 +++---
 ipaserver/install/krbinstance.py                   |   6 +-
 ipaserver/install/opendnssecinstance.py            |   7 +-
 ipaserver/install/replication.py                   |   4 +-
 ipaserver/install/server/install.py                |  13 +-
 ipatests/test_cmdline/test_ipagetkeytab.py         |  10 +-
 ipatests/test_ipapython/test_ipautil.py            |  61 +++++++++
 ipatests/test_ipapython/test_keyring.py            |  17 +--
 ipatests/test_xmlrpc/test_host_plugin.py           |   2 +-
 28 files changed, 464 insertions(+), 246 deletions(-)

diff --git a/install/certmonger/dogtag-ipa-ca-renew-agent-submit b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
index 44993b038a38da60a25843147e86b64deda874e1..5a6b7fa2285480d76f7ecb66152692b0722d08d7 100755
--- a/install/certmonger/dogtag-ipa-ca-renew-agent-submit
+++ b/install/certmonger/dogtag-ipa-ca-renew-agent-submit
@@ -156,15 +156,23 @@ def request_cert():
     args = [path] + sys.argv[1:]
     if os.environ.get('CERTMONGER_CA_PROFILE') == 'caCACert':
         args += ['-N', '-O', 'bypassCAnotafter=true']
-    stdout, stderr, rc = ipautil.run(args, raiseonerr=False, env=os.environ)
-    sys.stderr.write(stderr)
+    result = ipautil.run(args, raiseonerr=False, env=os.environ,
+                        capture_output=True)
+    if six.PY2:
+        sys.stderr.write(result.raw_error_output)
+    else:
+        # Write bytes directly
+        sys.stderr.buffer.write(result.raw_error_output)  #pylint: disable=no-member
     sys.stderr.flush()
 
-    syslog.syslog(syslog.LOG_NOTICE, "dogtag-ipa-renew-agent returned %d" % rc)
+    syslog.syslog(syslog.LOG_NOTICE,
+                  "dogtag-ipa-renew-agent returned %d" % result.returncode)
 
+    stdout = result.output
     if stdout.endswith('\n'):
         stdout = stdout[:-1]
 
+    rc = result.returncode
     if rc == WAIT_WITH_DELAY:
         delay, sep, cookie = stdout.partition('\n')
         return (rc, delay, cookie)
diff --git a/install/certmonger/ipa-server-guard b/install/certmonger/ipa-server-guard
index 7ce3e43fce16ce9974e5db10af7cf851c0411943..2c5409718543f714b791ac75bb2612d25a68db5c 100755
--- a/install/certmonger/ipa-server-guard
+++ b/install/certmonger/ipa-server-guard
@@ -30,6 +30,8 @@ import sys
 import syslog
 import traceback
 
+import six
+
 from ipapython import ipautil
 from ipaserver.install import certs
 
@@ -39,14 +41,18 @@ def main():
         raise RuntimeError("Not enough arguments")
 
     with certs.renewal_lock:
-        stdout, stderr, rc = ipautil.run(sys.argv[1:], raiseonerr=False,
-                                         env=os.environ)
-        sys.stdout.write(stdout)
+        result = ipautil.run(sys.argv[1:], raiseonerr=False, env=os.environ)
+        if six.PY2:
+            sys.stdout.write(result.raw_output)
+            sys.stderr.write(result.raw_error_output)
+        else:
+            # Write bytes directly
+            sys.stdout.buffer.write(result.raw_output)  #pylint: disable=no-member
+            sys.stderr.buffer.write(result.raw_error_output)  #pylint: disable=no-member
         sys.stdout.flush()
-        sys.stderr.write(stderr)
         sys.stderr.flush()
 
-    return rc
+    return result.returncode
 
 
 try:
diff --git a/install/oddjob/com.redhat.idm.trust-fetch-domains b/install/oddjob/com.redhat.idm.trust-fetch-domains
index 019545b9340ac95f4e528618875df3dcd8170feb..ea82e086ef5bade9be3b9f30ae50504c4fcd5db7 100755
--- a/install/oddjob/com.redhat.idm.trust-fetch-domains
+++ b/install/oddjob/com.redhat.idm.trust-fetch-domains
@@ -26,9 +26,8 @@ def retrieve_keytab(api, ccache_name, oneway_keytab_name, oneway_principal):
     if os.path.isfile(oneway_keytab_name):
         os.unlink(oneway_keytab_name)
 
-    (stdout, stderr, retcode) = ipautil.run(getkeytab_args,
-                                            env={'KRB5CCNAME': ccache_name, 'LANG': 'C'},
-                                            raiseonerr=False)
+    ipautil.run(getkeytab_args, env={'KRB5CCNAME': ccache_name, 'LANG': 'C'},
+                raiseonerr=False)
     # Make sure SSSD is able to read the keytab
     try:
         sssd = pwd.getpwnam('sssd')
diff --git a/install/tools/ipa-replica-conncheck b/install/tools/ipa-replica-conncheck
index a67837c545a9d3a4b328925483d65592ebe94ed1..ebcbb91e3989683dcd3e2ef87bab15b79c452093 100755
--- a/install/tools/ipa-replica-conncheck
+++ b/install/tools/ipa-replica-conncheck
@@ -80,7 +80,8 @@ class SshExec(object):
         elif 'KRB5CCNAME' in os.environ:
             env['KRB5CCNAME'] = os.environ['KRB5CCNAME']
 
-        return ipautil.run(cmd, env=env, raiseonerr=False)
+        return ipautil.run(cmd, env=env, raiseonerr=False,
+                           capture_output=True, capture_error=True)
 
 
 class CheckedPort(object):
@@ -402,40 +403,40 @@ def main():
                         sys.exit("Principal password required")
 
 
-                stderr=''
-                (stdout, stderr, returncode) = ipautil.run([paths.KINIT, principal],
+                result = ipautil.run([paths.KINIT, principal],
                      env={'KRB5_CONFIG':KRB5_CONFIG, 'KRB5CCNAME':CCACHE_FILE},
-                    stdin=password, raiseonerr=False)
-                if returncode != 0:
-                    raise RuntimeError("Cannot acquire Kerberos ticket: %s" % stderr)
+                    stdin=password, raiseonerr=False, capture_error=True)
+                if result.returncode != 0:
+                    raise RuntimeError("Cannot acquire Kerberos ticket: %s" %
+                                        result.error_output)
 
                 # Verify kinit was actually successful
-                stderr=''
-                (stdout, stderr, returncode) = ipautil.run([paths.BIN_KVNO,
+                result = ipautil.run([paths.BIN_KVNO,
                      'host/%s' % options.master],
                     env={'KRB5_CONFIG':KRB5_CONFIG, 'KRB5CCNAME':CCACHE_FILE},
-                    raiseonerr=False)
-                if returncode != 0:
-                    raise RuntimeError("Could not get ticket for master server: %s" % stderr)
+                    raiseonerr=False, capture_error=True)
+                if result.returncode != 0:
+                    raise RuntimeError("Could not get ticket for master server: %s" %
+                                        result.error_output)
 
             user = principal.partition('@')[0]
             ssh = SshExec(user, options.master)
 
             print_info("Check SSH connection to remote master")
-            stdout, stderr, returncode = ssh('echo OK', verbose=True)
-            if returncode != 0:
+            result = ssh('echo OK', verbose=True)
+            if result.returncode != 0:
                 print('Could not SSH into remote host. Error output:')
-                for line in stderr.splitlines():
+                for line in result.error_output.splitlines():
                     print('    %s' % line)
                 raise RuntimeError('Could not SSH to remote host.')
 
             print_info("Execute check on remote master")
-            stdout, stderr, returncode = ssh(
+            result = ssh(
                 "/usr/sbin/ipa-replica-conncheck " +
                     " ".join(remote_check_opts))
-            print_info(stdout)
-            if returncode != 0:
-                raise RuntimeError("Remote master check failed with following error message(s):\n%s" % stderr)
+            print_info(result.output)
+            if result.returncode != 0:
+                raise RuntimeError("Remote master check failed with following error message(s):\n%s" % result.error_output)
         else:
             # wait until user  test is ready
             print_info("Listeners are started. Use CTRL+C to terminate the listening part after the test.")
diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install
index 974dd1da8bf3f5836170ca67d2f4c298e7ec6844..bd5dc20543dd5641de30683ffb4e98786865bf6c 100755
--- a/ipa-client/ipa-install/ipa-client-install
+++ b/ipa-client/ipa-install/ipa-client-install
@@ -603,9 +603,9 @@ def uninstall(options, env):
         if options.debug:
             join_args.append("-d")
             env['XMLRPC_TRACE_CURL'] = 'yes'
-        (stdout, stderr, returncode) = run(join_args, raiseonerr=False, env=env)
-        if returncode != 0:
-            root_logger.error("Unenrolling host failed: %s", stderr)
+        result = run(join_args, raiseonerr=False, env=env)
+        if result.returncode != 0:
+            root_logger.error("Unenrolling host failed: %s", result.error_log)
 
     if os.path.exists(paths.IPA_DEFAULT_CONF):
         root_logger.info(
@@ -1438,8 +1438,8 @@ def configure_sshd_config(fstore, options):
                 args.append('-o')
                 args.append('%s=%s' % item)
 
-            (stdout, stderr, retcode) = ipautil.run(args, raiseonerr=False)
-            if retcode == 0:
+            result = ipautil.run(args, raiseonerr=False)
+            if result.returncode == 0:
                 authorized_keys_changes = candidate
                 break
 
@@ -1475,11 +1475,11 @@ def configure_automount(options):
         args.append('--no-sssd')
 
     try:
-        stdout, _, _ = run(args)
+        result = run(args)
     except Exception as e:
         root_logger.error('Automount configuration failed: %s', str(e))
     else:
-        root_logger.info(stdout)
+        root_logger.info(result.output_log)
 
 
 def configure_nisdomain(options, domain):
@@ -1491,9 +1491,12 @@ def configure_nisdomain(options, domain):
     # First backup the old NIS domain name
     if os.path.exists(paths.BIN_NISDOMAINNAME):
         try:
-            nis_domain_name, _, _ = ipautil.run([paths.BIN_NISDOMAINNAME])
+            result = ipautil.run([paths.BIN_NISDOMAINNAME],
+                                 capture_output=True)
         except CalledProcessError as e:
             pass
+        else:
+            nis_domain_name = result.output
 
     statestore.backup_state('network', 'nisdomain', nis_domain_name)
 
@@ -1530,8 +1533,9 @@ def unconfigure_nisdomain():
 
 
 def get_iface_from_ip(ip_addr):
-    ipresult = ipautil.run([paths.IP, '-oneline', 'address', 'show'])
-    for line in ipresult[0].split('\n'):
+    result = ipautil.run([paths.IP, '-oneline', 'address', 'show'],
+                         capture_output=True)
+    for line in result.output.split('\n'):
         fields = line.split()
         if len(fields) < 6:
             continue
@@ -1548,8 +1552,8 @@ def get_local_ipaddresses(iface=None):
     args = [paths.IP, '-oneline', 'address', 'show']
     if iface:
         args += ['dev', iface]
-    ipresult = ipautil.run(args)
-    lines = ipresult[0].split('\n')
+    result = ipautil.run(args, capture_output=True)
+    lines = result.output.split('\n')
     ips = []
     for line in lines:
         fields = line.split()
@@ -1922,9 +1926,10 @@ def get_ca_certs_from_http(url, warn=True):
     root_logger.debug("trying to retrieve CA cert via HTTP from %s", url)
     try:
 
-        stdout, stderr, rc = run([paths.BIN_WGET, "-O", "-", url])
+        result = run([paths.BIN_WGET, "-O", "-", url], capture_output=True)
     except CalledProcessError as e:
         raise errors.NoCertificateError(entry=url)
+    stdout = result.output
 
     try:
         certs = x509.load_certificate_list(stdout)
@@ -2671,12 +2676,15 @@ def install(options, env, fstore, statestore):
                 return CLIENT_INSTALL_ERROR
 
             # Now join the domain
-            (stdout, stderr, returncode) = run(join_args, raiseonerr=False, env=env, nolog=nolog)
+            result = run(
+                join_args, raiseonerr=False, env=env, nolog=nolog,
+                capture_error=True)
+            stderr = result.error_output
 
-            if returncode != 0:
+            if result.returncode != 0:
                 root_logger.error("Joining realm failed: %s", stderr)
                 if not options.force:
-                    if returncode == 13:
+                    if result.returncode == 13:
                         root_logger.info("Use --force-join option to override "
                                          "the host entry on the server "
                                          "and force client enrollment.")
@@ -2694,8 +2702,7 @@ def install(options, env, fstore, statestore):
                 subject_base = DN(subject_base)
 
             if options.principal is not None:
-                stderr, stdout, returncode = run(
-                        ["kdestroy"], raiseonerr=False, env=env)
+                run(["kdestroy"], raiseonerr=False, env=env)
 
             # Obtain the TGT. We do it with the temporary krb5.conf, so that
             # only the KDC we're installing under is contacted.
diff --git a/ipalib/plugins/pwpolicy.py b/ipalib/plugins/pwpolicy.py
index 5e98d5469eac0a3f0094d0485dc5d0aaee299438..7bd3c0984fde5c3182f229a545b487614106a128 100644
--- a/ipalib/plugins/pwpolicy.py
+++ b/ipalib/plugins/pwpolicy.py
@@ -279,9 +279,9 @@ class pwpolicy(LDAPObject):
     has_lockout = False
     lockout_params = ()
 
-    (stdout, stderr, rc) = run(['klist', '-V'], raiseonerr=False)
-    if rc == 0:
-        verstr = stdout.split()[-1]
+    result = run(['klist', '-V'], raiseonerr=False, capture_output=True)
+    if result.returncode == 0:
+        verstr = result.output.split()[-1]
         ver = version.LooseVersion(verstr)
         min = version.LooseVersion(MIN_KRB5KDC_WITH_LOCKOUT)
         if ver >= min:
diff --git a/ipaplatform/base/services.py b/ipaplatform/base/services.py
index da2f1011e34431664cd5c730668ae483b7bd0a1d..0ca9a7abea329827cbfa05e6b74bed8d8ed3e1fc 100644
--- a/ipaplatform/base/services.py
+++ b/ipaplatform/base/services.py
@@ -281,7 +281,7 @@ class SystemdService(PlatformService):
         if instance == "ipa-otpd.socket":
             args.append("--ignore-dependencies")
 
-        ipautil.run(args, capture_output=capture_output)
+        ipautil.run(args, skip_output=not capture_output)
 
         if getattr(self.api.env, 'context', None) in ['ipactl', 'installer']:
             update_service_list = True
@@ -294,7 +294,7 @@ class SystemdService(PlatformService):
     def start(self, instance_name="", capture_output=True, wait=True):
         ipautil.run([paths.SYSTEMCTL, "start",
                      self.service_instance(instance_name)],
-                     capture_output=capture_output)
+                     skip_output=not capture_output)
 
         if getattr(self.api.env, 'context', None) in ['ipactl', 'installer']:
             update_service_list = True
@@ -310,7 +310,7 @@ class SystemdService(PlatformService):
     def restart(self, instance_name="", capture_output=True, wait=True):
         ipautil.run([paths.SYSTEMCTL, "restart",
                      self.service_instance(instance_name)],
-                    capture_output=capture_output)
+                    skip_output=not capture_output)
 
         if wait and self.is_running(instance_name):
             self.wait_for_open_ports(self.service_instance(instance_name))
@@ -320,7 +320,7 @@ class SystemdService(PlatformService):
 
         while True:
             try:
-                (sout, serr, rcode) = ipautil.run(
+                result = ipautil.run(
                     [paths.SYSTEMCTL, "is-active", instance],
                     capture_output=True
                 )
@@ -331,24 +331,24 @@ class SystemdService(PlatformService):
                 return False
             else:
                 # activating
-                if rcode == 3 and 'activating' in str(sout):
+                if result.returncode == 3 and 'activating' in result.output:
                     time.sleep(SERVICE_POLL_INTERVAL)
                     continue
                 # active
-                if rcode == 0:
+                if result.returncode == 0:
                     return True
                 # not active
                 return False
 
     def is_installed(self):
         try:
-            (sout, serr, rcode) = ipautil.run([paths.SYSTEMCTL,
-                                               "list-unit-files",
-                                               "--full"])
-            if rcode != 0:
+            result = ipautil.run(
+                [paths.SYSTEMCTL, "list-unit-files", "--full"],
+                capture_output=True)
+            if result.returncode != 0:
                 return False
             else:
-                svar = self.parse_variables(sout)
+                svar = self.parse_variables(result.output)
                 if not self.service_instance("") in svar:
                     # systemd doesn't show the service
                     return False
@@ -360,12 +360,11 @@ class SystemdService(PlatformService):
     def is_enabled(self, instance_name=""):
         enabled = True
         try:
-            (sout, serr, rcode) = ipautil.run(
-                                      [paths.SYSTEMCTL,
-                                       "is-enabled",
-                                        self.service_instance(instance_name)])
+            result = ipautil.run(
+                [paths.SYSTEMCTL, "is-enabled",
+                 self.service_instance(instance_name)])
 
-            if rcode != 0:
+            if result.returncode != 0:
                 enabled = False
 
         except ipautil.CalledProcessError:
@@ -375,12 +374,12 @@ class SystemdService(PlatformService):
     def is_masked(self, instance_name=""):
         masked = False
         try:
-            (sout, serr, rcode) = ipautil.run(
-                                      [paths.SYSTEMCTL,
-                                       "is-enabled",
-                                        self.service_instance(instance_name)])
+            result = ipautil.run(
+                [paths.SYSTEMCTL, "is-enabled",
+                 self.service_instance(instance_name)],
+                capture_output=True)
 
-            if rcode == 1 and sout == 'masked':
+            if result.returncode == 1 and result.output == 'masked':
                 masked = True
 
         except ipautil.CalledProcessError:
diff --git a/ipaplatform/redhat/services.py b/ipaplatform/redhat/services.py
index 0902215a56191032a1a65d0c2d05ddd5b7dab67f..621128581e7e315da1cffb797a52bb25009cb216 100644
--- a/ipaplatform/redhat/services.py
+++ b/ipaplatform/redhat/services.py
@@ -220,9 +220,9 @@ class RedHatCAService(RedHatService):
                     url
                 ]
 
-                stdout, stderr, returncode = ipautil.run(args)
+                result = ipautil.run(args, capture_output=True)
 
-                status = dogtag._parse_ca_status(stdout)
+                status = dogtag._parse_ca_status(result.output)
                 # end of workaround
             except Exception as e:
                 status = 'check interrupted due to error: %s' % e
diff --git a/ipaplatform/redhat/tasks.py b/ipaplatform/redhat/tasks.py
index 94d2cb4e906965a20bcfdd55f38854005091c26f..099eb9e3bdb74e6726d55bfd12590a47a963a684 100644
--- a/ipaplatform/redhat/tasks.py
+++ b/ipaplatform/redhat/tasks.py
@@ -375,8 +375,11 @@ class RedHatTaskNamespace(BaseTaskNamespace):
             if state is None:
                 continue
             try:
-                (stdout, stderr, rc) = ipautil.run([paths.GETSEBOOL, setting])
-                original_state = stdout.split()[2]
+                result = ipautil.run(
+                    [paths.GETSEBOOL, setting],
+                    capture_output=True
+                )
+                original_state = result.output.split()[2]
                 if backup_func is not None:
                     backup_func(setting, original_state)
 
diff --git a/ipapython/certdb.py b/ipapython/certdb.py
index 704bae528b44a3a3b978d25982a7e75a8e3af0f6..305812183971c05b932692cf4bc04ab4463878f6 100644
--- a/ipapython/certdb.py
+++ b/ipapython/certdb.py
@@ -107,10 +107,10 @@ class NSSDatabase(object):
     def __exit__(self, type, value, tb):
         self.close()
 
-    def run_certutil(self, args, stdin=None):
+    def run_certutil(self, args, stdin=None, **kwargs):
         new_args = [paths.CERTUTIL, "-d", self.secdir]
         new_args = new_args + args
-        return ipautil.run(new_args, stdin)
+        return ipautil.run(new_args, stdin, **kwargs)
 
     def create_db(self, password_filename):
         """Create cert DB
@@ -124,8 +124,8 @@ class NSSDatabase(object):
 
         :return: List of (name, trust_flags) tuples
         """
-        certs, stderr, returncode = self.run_certutil(["-L"])
-        certs = certs.splitlines()
+        result = self.run_certutil(["-L"], capture_output=True)
+        certs = result.output.splitlines()
 
         # FIXME, this relies on NSS never changing the formatting of certutil
         certlist = []
@@ -157,9 +157,8 @@ class NSSDatabase(object):
         :return: List of certificate names
         """
         root_nicknames = []
-        chain, stderr, returncode = self.run_certutil([
-            "-O", "-n", nickname])
-        chain = chain.splitlines()
+        result = self.run_certutil(["-O", "-n", nickname], capture_output=True)
+        chain = result.output.splitlines()
 
         for c in chain:
             m = re.match('\s*"(.*)" \[.*', c)
@@ -247,7 +246,8 @@ class NSSDatabase(object):
                             '-print_certs',
                         ]
                         try:
-                            stdout, stderr, rc = ipautil.run(args, stdin=body)
+                            result = ipautil.run(
+                                args, stdin=body, capture_output=True)
                         except ipautil.CalledProcessError as e:
                             if label == 'CERTIFICATE':
                                 root_logger.warning(
@@ -259,7 +259,7 @@ class NSSDatabase(object):
                                     filename, line, e)
                             continue
                         else:
-                            extracted_certs += stdout + '\n'
+                            extracted_certs += result.output + '\n'
                             loaded = True
                             continue
 
@@ -286,14 +286,15 @@ class NSSDatabase(object):
                                 '-passin', 'file:' + key_pwdfile.name,
                             ]
                         try:
-                            stdout, stderr, rc = ipautil.run(args, stdin=body)
+                            result = ipautil.run(
+                                args, stdin=body, capture_output=True)
                         except ipautil.CalledProcessError as e:
                             root_logger.warning(
                                 "Skipping private key in %s at line %s: %s",
                                 filename, line, e)
                             continue
                         else:
-                            extracted_key = stdout
+                            extracted_key = result.output
                             key_file = filename
                             loaded = True
                             continue
@@ -401,10 +402,10 @@ class NSSDatabase(object):
         else:
             args.append('-r')
         try:
-            cert, err, returncode = self.run_certutil(args)
+            result = self.run_certutil(args, capture_output=True)
         except ipautil.CalledProcessError:
             raise RuntimeError("Failed to get %s" % nickname)
-        return cert
+        return result.output
 
     def has_nickname(self, nickname):
         try:
diff --git a/ipapython/dnssec/bindmgr.py b/ipapython/dnssec/bindmgr.py
index 1822dacf2535e7c37062e4d639e01289edcf5074..a0a9f2eb28ddb9dce8d1d71ab10afe6a0e0146ec 100644
--- a/ipapython/dnssec/bindmgr.py
+++ b/ipapython/dnssec/bindmgr.py
@@ -40,8 +40,8 @@ class BINDMgr(object):
 
     def notify_zone(self, zone):
         cmd = ['rndc', 'sign', zone.to_text()]
-        output = ipautil.run(cmd)[0]
-        self.log.info(output)
+        result = ipautil.run(cmd, capture_output=True)
+        self.log.info('%s', result.output_log)
 
     def dn2zone_name(self, dn):
         """cn=KSK-20140813162153Z-cede9e182fc4af76c4bddbc19123a565,cn=keys,idnsname=test,cn=dns,dc=ipa,dc=example"""
@@ -110,7 +110,8 @@ class BINDMgr(object):
         cmd.append(zone.to_text())
 
         # keys has to be readable by ODS & named
-        basename = ipautil.run(cmd)[0].strip()
+        result = ipautil.run(cmd, capture_output=True)
+        basename = result.output.strip()
         private_fn = "%s/%s.private" % (workdir, basename)
         os.chmod(private_fn, FILE_PERM)
         # this is useful mainly for debugging
diff --git a/ipapython/dnssec/odsmgr.py b/ipapython/dnssec/odsmgr.py
index efbe16cc6ebf050d9cf347ed97b2b2e4b37c8a6e..ebcd3aa242531689fee697b6d12d0269e5dba27e 100644
--- a/ipapython/dnssec/odsmgr.py
+++ b/ipapython/dnssec/odsmgr.py
@@ -128,7 +128,8 @@ class ODSMgr(object):
         Raises CalledProcessError if returncode != 0.
         """
         cmd = ['ods-ksmutil'] + params
-        return ipautil.run(cmd)[0]
+        result = ipautil.run(cmd, capture_output=True)
+        return result.output
 
     def get_ods_zonelist(self):
         stdout = self.ksmutil(['zonelist', 'export'])
diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
index 89047b2e8ea16d14a6634e551c49abe240c54009..92b56663e5dd61674691ab7fd7446a3e9b97df12 100644
--- a/ipapython/ipautil.py
+++ b/ipapython/ipautil.py
@@ -37,6 +37,8 @@ import gssapi
 import pwd
 import grp
 from contextlib import contextmanager
+import locale
+import collections
 
 from dns import resolver, rdatatype
 from dns.exception import DNSException
@@ -155,8 +157,10 @@ class CheckedIPAddress(netaddr.IPAddress):
             elif addr.version == 6:
                 family = 'inet6'
 
-            ipresult = run([paths.IP, '-family', family, '-oneline', 'address', 'show'])
-            lines = ipresult[0].split('\n')
+            result = run(
+                [paths.IP, '-family', family, '-oneline', 'address', 'show'],
+                capture_output=True)
+            lines = result.output.split('\n')
             for line in lines:
                 fields = line.split()
                 if len(fields) < 4:
@@ -256,13 +260,32 @@ def write_tmp_file(txt):
     return fd
 
 def shell_quote(string):
-    return "'" + string.replace("'", "'\\''") + "'"
+    if isinstance(string, str):
+        return "'" + string.replace("'", "'\\''") + "'"
+    else:
+        return b"'" + string.replace(b"'", b"'\\''") + b"'"
 
-def run(args, stdin=None, raiseonerr=True,
-        nolog=(), env=None, capture_output=True, skip_output=False, cwd=None,
-        runas=None, timeout=None, suplementary_groups=[]):
+if six.PY3:
+    def _log_arg(s):
+        """Convert string or bytes to a string suitable for logging"""
+        if isinstance(s, bytes):
+            return s.decode(locale.getpreferredencoding(),
+                            errors='replace')
+        else:
+            return s
+else:
+    _log_arg = str
+
+class _RunResult(collections.namedtuple('_RunResult',
+                                       'output error_output returncode')):
+    """Result of ipautil.run"""
+
+def run(args, stdin=None, raiseonerr=True, nolog=(), env=None,
+        capture_output=False, skip_output=False, cwd=None,
+        runas=None, timeout=None, suplementary_groups=[],
+        capture_error=False, encoding=None):
     """
-    Execute a command and return stdin, stdout and the process return code.
+    Execute an external command.
 
     :param args: List of arguments for the command
     :param stdin: Optional input to the command
@@ -283,8 +306,8 @@ def run(args, stdin=None, raiseonerr=True,
         If a value isn't found in the list it is silently ignored.
     :param env: Dictionary of environment variables passed to the command.
         When None, current environment is copied
-    :param capture_output: Capture stderr and stdout
-    :param skip_output: Redirect the output to /dev/null and do not capture it
+    :param capture_output: Capture stdout
+    :param skip_output: Redirect the output to /dev/null and do not log it
     :param cwd: Current working directory
     :param runas: Name of a user that the command should be run as. The spawned
         process will have both real and effective UID and GID set.
@@ -293,6 +316,30 @@ def run(args, stdin=None, raiseonerr=True,
     :param suplementary_groups: List of group names that will be used as
         suplementary groups for subporcess.
         The option runas must be specified together with this option.
+    :param capture_error: Capture stderr
+    :param encoding: For Python 3, the encoding to use for output,
+        error_output, and (if it's not bytes) stdin.
+        If None, the current encoding according to locale is used.
+
+    :return: An object with these attributes:
+
+        `returncode`: The process' exit status
+
+        `output` and `error_output`: captured output, as strings. Under
+        Python 3, these are encoded with the given `encoding`.
+        None unless `capture_output` or `capture_error`, respectively, are given
+
+        `raw_output`, `raw_error_output`: captured output, as bytes.
+
+        `output_log` and `error_log`: The captured output, as strings, with any
+        unencodable characters discarded. These should only be used
+        for logging or error messages.
+
+    If skip_output is given, all output-related attributes on the result
+    (that is, all except `returncode`) are None.
+
+    For backwards compatibility, the return value can also be used as a
+    (output, error_output, returncode) triple.
     """
     assert isinstance(suplementary_groups, list)
     p_in = None
@@ -301,12 +348,16 @@ def run(args, stdin=None, raiseonerr=True,
 
     if isinstance(nolog, six.string_types):
         # We expect a tuple (or list, or other iterable) of nolog strings.
-        # Passing just a single string is bad: strings are also, so this
+        # Passing just a single string is bad: strings are iterable, so this
         # would result in every individual character of that string being
         # replaced by XXXXXXXX.
         # This is a sanity check to prevent that.
         raise ValueError('nolog must be a tuple of strings.')
 
+    if skip_output and (capture_output or capture_error):
+        raise ValueError('skip_output is incompatible with '
+                         'capture_output or capture_error')
+
     if env is None:
         # copy default env
         env = copy.deepcopy(os.environ)
@@ -315,16 +366,22 @@ def run(args, stdin=None, raiseonerr=True,
         p_in = subprocess.PIPE
     if skip_output:
         p_out = p_err = open(paths.DEV_NULL, 'w')
-    elif capture_output:
+    else:
         p_out = subprocess.PIPE
         p_err = subprocess.PIPE
 
+    if encoding is None:
+        encoding = locale.getpreferredencoding()
+
+    if six.PY3 and isinstance(stdin, str):
+        stdin = stdin.encode(encoding)
+
     if timeout:
         # If a timeout was provided, use the timeout command
         # to execute the requested command.
         args[0:0] = [paths.BIN_TIMEOUT, str(timeout)]
 
-    arg_string = nolog_replace(' '.join(shell_quote(a) for a in args), nolog)
+    arg_string = nolog_replace(' '.join(_log_arg(a) for a in args), nolog)
     root_logger.debug('Starting external process')
     root_logger.debug('args=%s' % arg_string)
 
@@ -352,8 +409,7 @@ def run(args, stdin=None, raiseonerr=True,
         p = subprocess.Popen(args, stdin=p_in, stdout=p_out, stderr=p_err,
                              close_fds=True, env=env, cwd=cwd,
                              preexec_fn=preexec_fn)
-        stdout,stderr = p.communicate(stdin)
-        stdout,stderr = str(stdout), str(stderr)    # Make pylint happy
+        stdout, stderr = p.communicate(stdin)
     except KeyboardInterrupt:
         root_logger.debug('Process interrupted')
         p.wait()
@@ -372,16 +428,50 @@ def run(args, stdin=None, raiseonerr=True,
 
     # The command and its output may include passwords that we don't want
     # to log. Replace those.
-    if capture_output and not skip_output:
-        stdout = nolog_replace(stdout, nolog)
-        stderr = nolog_replace(stderr, nolog)
-        root_logger.debug('stdout=%s' % stdout)
-        root_logger.debug('stderr=%s' % stderr)
+    if skip_output:
+        output_log = None
+        error_log = None
+    else:
+        if six.PY3:
+            output_log = stdout.decode(locale.getpreferredencoding(),
+                                          errors='replace')
+        else:
+            output_log = stdout
+        if six.PY3:
+            error_log = stderr.decode(locale.getpreferredencoding(),
+                                          errors='replace')
+        else:
+            error_log = stderr
+        output_log = nolog_replace(output_log, nolog)
+        root_logger.debug('stdout=%s' % output_log)
+        error_log = nolog_replace(error_log, nolog)
+        root_logger.debug('stderr=%s' % error_log)
+
+    if capture_output:
+        if six.PY2:
+            output = stdout
+        else:
+            output = stdout.encode(encoding)
+    else:
+        output = None
+
+    if capture_error:
+        if six.PY2:
+            error_output = stderr
+        else:
+            error_output = stderr.encode(encoding)
+    else:
+        error_output = None
 
     if p.returncode != 0 and raiseonerr:
-        raise CalledProcessError(p.returncode, arg_string, stdout)
+        raise CalledProcessError(p.returncode, arg_string, str(output))
 
-    return (stdout, stderr, p.returncode)
+    result = _RunResult(output, error_output, p.returncode)
+    result.raw_output = stdout
+    result.raw_error_output = stderr
+    result.output_log = output_log
+    result.error_log = error_log
+    return result
 
 
 def nolog_replace(string, nolog):
@@ -1269,10 +1359,10 @@ def kinit_password(principal, password, ccache_name, config=None,
 
     # this workaround enables us to capture stderr and put it
     # into the raised exception in case of unsuccessful authentication
-    (stdout, stderr, retcode) = run(args, stdin=password, env=env,
-                                    raiseonerr=False)
-    if retcode:
-        raise RuntimeError(stderr)
+    result = run(args, stdin=password, env=env, raiseonerr=False,
+                 capture_error=True)
+    if result.returncode:
+        raise RuntimeError(result.error_output)
 
 
 def dn_attribute_property(private_name):
diff --git a/ipapython/kernel_keyring.py b/ipapython/kernel_keyring.py
index d30531cabaee5c12376f0821a21a6f63cd60397c..870815ed389a8fc86cebac2adb4827a769642bb8 100644
--- a/ipapython/kernel_keyring.py
+++ b/ipapython/kernel_keyring.py
@@ -36,24 +36,29 @@ def dump_keys():
     """
     Dump all keys
     """
-    (stdout, stderr, rc) = run(['keyctl', 'list', KEYRING], raiseonerr=False)
-    return stdout
+    result = run(['keyctl', 'list', KEYRING], raiseonerr=False,
+                 capture_output=True)
+    return result.output
 
 def get_real_key(key):
     """
     One cannot request a key based on the description it was created with
     so find the one we're looking for.
     """
-    (stdout, stderr, rc) = run(['keyctl', 'search', KEYRING, KEYTYPE, key], raiseonerr=False)
-    if rc:
+    assert isinstance(key, str)
+    result = run(['keyctl', 'search', KEYRING, KEYTYPE, key],
+                 raiseonerr=False, capture_output=True)
+    if result.returncode:
         raise ValueError('key %s not found' % key)
-    return stdout.rstrip()
+    return result.output.rstrip()
 
 def get_persistent_key(key):
-    (stdout, stderr, rc) = run(['keyctl', 'get_persistent', KEYRING, key], raiseonerr=False)
-    if rc:
+    assert isinstance(key, str)
+    result= run(['keyctl', 'get_persistent', KEYRING, key],
+                raiseonerr=False, capture_output=True)
+    if result.returncode:
         raise ValueError('persistent key %s not found' % key)
-    return stdout.rstrip()
+    return result.output.rstrip()
 
 def is_persistent_keyring_supported():
     uid = os.geteuid()
@@ -68,6 +73,7 @@ def has_key(key):
     """
     Returns True/False whether the key exists in the keyring.
     """
+    assert isinstance(key, str)
     try:
         get_real_key(key)
         return True
@@ -80,22 +86,27 @@ def read_key(key):
 
     Use pipe instead of print here to ensure we always get the raw data.
     """
+    assert isinstance(key, str)
     real_key = get_real_key(key)
-    (stdout, stderr, rc) = run(['keyctl', 'pipe', real_key], raiseonerr=False)
-    if rc:
-        raise ValueError('keyctl pipe failed: %s' % stderr)
+    result = run(['keyctl', 'pipe', real_key], raiseonerr=False,
+                 capture_output=True)
+    if result.returncode:
+        raise ValueError('keyctl pipe failed: %s' % result.error_log)
 
-    return stdout
+    return result.output
 
 def update_key(key, value):
     """
     Update the keyring data. If they key doesn't exist it is created.
     """
+    assert isinstance(key, str)
+    assert isinstance(value, bytes)
     if has_key(key):
         real_key = get_real_key(key)
-        (stdout, stderr, rc) = run(['keyctl', 'pupdate', real_key], stdin=value, raiseonerr=False)
-        if rc:
-            raise ValueError('keyctl pupdate failed: %s' % stderr)
+        result = run(['keyctl', 'pupdate', real_key], stdin=value,
+                     raiseonerr=False)
+        if result.returncode:
+            raise ValueError('keyctl pupdate failed: %s' % result.error_log)
     else:
         add_key(key, value)
 
@@ -103,17 +114,22 @@ def add_key(key, value):
     """
     Add a key to the kernel keyring.
     """
+    assert isinstance(key, str)
+    assert isinstance(value, bytes)
     if has_key(key):
         raise ValueError('key %s already exists' % key)
-    (stdout, stderr, rc) = run(['keyctl', 'padd', KEYTYPE, key, KEYRING], stdin=value, raiseonerr=False)
-    if rc:
-        raise ValueError('keyctl padd failed: %s' % stderr)
+    result = run(['keyctl', 'padd', KEYTYPE, key, KEYRING],
+                 stdin=value, raiseonerr=False)
+    if result.returncode:
+        raise ValueError('keyctl padd failed: %s' % result.error_log)
 
 def del_key(key):
     """
     Remove a key from the keyring
     """
+    assert isinstance(key, str)
     real_key = get_real_key(key)
-    (stdout, stderr, rc) = run(['keyctl', 'unlink', real_key, KEYRING], raiseonerr=False)
-    if rc:
-        raise ValueError('keyctl unlink failed: %s' % stderr)
+    result = run(['keyctl', 'unlink', real_key, KEYRING],
+                 raiseonerr=False)
+    if result.returncode:
+        raise ValueError('keyctl unlink failed: %s' % result.error_log)
diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index 2e412861ebc265a9b07c8634068151181a3e9b9e..bb58945ef828b69b89effe46169697db8c218458 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -588,7 +588,7 @@ class DomainValidator(object):
         # Destroy the contents of the ccache
         root_logger.debug('Destroying the contents of the separate ccache')
 
-        (stdout, stderr, returncode) = ipautil.run(
+        ipautil.run(
             [paths.KDESTROY, '-A', '-c', ccache_path],
             env={'KRB5CCNAME': ccache_path},
             raiseonerr=False)
@@ -597,16 +597,14 @@ class DomainValidator(object):
         root_logger.debug('Running kinit from ipa.keytab to obtain HTTP '
                           'service principal with MS-PAC attached.')
 
-        (stdout, stderr, returncode) = ipautil.run(
+        result = ipautil.run(
             [paths.KINIT, '-kt', keytab, principal],
             env={'KRB5CCNAME': ccache_path},
             raiseonerr=False)
 
-        if returncode == 0:
+        if result.returncode == 0:
             return (ccache_path, principal)
         else:
-            root_logger.debug('Kinit failed, stout: %s, stderr: %s'
-                              % (stdout, stderr))
             return (None, None)
 
     def kinit_as_administrator(self, domain):
@@ -634,7 +632,7 @@ class DomainValidator(object):
         # Destroy the contents of the ccache
         root_logger.debug('Destroying the contents of the separate ccache')
 
-        (stdout, stderr, returncode) = ipautil.run(
+        ipautil.run(
             [paths.KDESTROY, '-A', '-c', ccache_path],
             env={'KRB5CCNAME': ccache_path},
             raiseonerr=False)
@@ -642,17 +640,15 @@ class DomainValidator(object):
         # Destroy the contents of the ccache
         root_logger.debug('Running kinit with credentials of AD administrator')
 
-        (stdout, stderr, returncode) = ipautil.run(
+        result = ipautil.run(
             [paths.KINIT, principal],
             env={'KRB5CCNAME': ccache_path},
             stdin=password,
             raiseonerr=False)
 
-        if returncode == 0:
+        if result.returncode == 0:
             return (ccache_path, principal)
         else:
-            root_logger.debug('Kinit failed, stout: %s, stderr: %s'
-                              % (stdout, stderr))
             return (None, None)
 
     def search_in_dc(self, domain, filter, attrs, scope, basedn=None,
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index 2ca718a7b6799b7daf825918517a54852746a84f..c11b05a3e1fb9259ac1bb0ef0208379fb06f105d 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -549,10 +549,12 @@ class CAInstance(DogtagInstance):
             x509.write_certificate(cert.der_data, cert_file.name)
             cert_file.flush()
 
-            cert_chain, stderr, rc = ipautil.run(
+            result = ipautil.run(
                 [paths.OPENSSL, 'crl2pkcs7',
                  '-certfile', self.cert_chain_file,
-                 '-nocrl'])
+                 '-nocrl'],
+                capture_output=True)
+            cert_chain = result.output
             # Dogtag chokes on the header and footer, remove them
             # https://bugzilla.redhat.com/show_bug.cgi?id=1127838
             cert_chain = re.search(
@@ -621,11 +623,9 @@ class CAInstance(DogtagInstance):
 
         # Look through the cert chain to get all the certs we need to add
         # trust for
-        p = subprocess.Popen([paths.CERTUTIL, "-d", self.agent_db,
-                              "-O", "-n", "ipa-ca-agent"], stdout=subprocess.PIPE)
-
-        chain = p.stdout.read()
-        chain = chain.split("\n")
+        args = [paths.CERTUTIL, "-d", self.agent_db, "-O", "-n", "ipa-ca-agent"]
+        result = ipautil.run(args, capture_output=True)
+        chain = result.output.split("\n")
 
         root_nickname=[]
         for part in chain:
@@ -660,10 +660,11 @@ class CAInstance(DogtagInstance):
             '-r', '/ca/agent/ca/profileReview?requestId=%s' % self.requestId,
             '%s' % ipautil.format_netloc(self.fqdn, 8443),
         ]
-        (stdout, _stderr, _returncode) = ipautil.run(
-            args, nolog=(self.admin_password,))
+        result = ipautil.run(
+            args, nolog=(self.admin_password,),
+            capture_output=True)
 
-        data = stdout.split('\n')
+        data = result.output.split('\n')
         params = get_defList(data)
         params['requestId'] = find_substring(data, "requestId")
         params['op'] = 'approve'
@@ -682,10 +683,11 @@ class CAInstance(DogtagInstance):
             '-r', '/ca/agent/ca/profileProcess',
             '%s' % ipautil.format_netloc(self.fqdn, 8443),
         ]
-        (stdout, _stderr, _returncode) = ipautil.run(
-            args, nolog=(self.admin_password,))
+        result = ipautil.run(
+            args, nolog=(self.admin_password,),
+            capture_output=True)
 
-        data = stdout.split('\n')
+        data = result.output.split('\n')
         outputList = get_outputList(data)
 
         self.ra_cert = outputList['b64_cert']
@@ -776,14 +778,15 @@ class CAInstance(DogtagInstance):
 
         conn.disconnect()
 
-    def __run_certutil(self, args, database=None, pwd_file=None, stdin=None):
+    def __run_certutil(self, args, database=None, pwd_file=None, stdin=None,
+                       **kwargs):
         if not database:
             database = self.ra_agent_db
         if not pwd_file:
             pwd_file = self.ra_agent_pwd
         new_args = [paths.CERTUTIL, "-d", database, "-f", pwd_file]
         new_args = new_args + args
-        return ipautil.run(new_args, stdin, nolog=(pwd_file,))
+        return ipautil.run(new_args, stdin, nolog=(pwd_file,), **kwargs)
 
     def __create_ra_agent_db(self):
         if ipautil.file_exists(self.ra_agent_db + "/cert8.db"):
@@ -822,13 +825,14 @@ class CAInstance(DogtagInstance):
         # makes openssl throw up.
         data = base64.b64decode(chain)
 
-        (certlist, _stderr, _returncode) = ipautil.run(
+        result = ipautil.run(
             [paths.OPENSSL,
              "pkcs7",
              "-inform",
              "DER",
              "-print_certs",
-             ], stdin=data)
+             ], stdin=data, capture_output=True)
+        certlist = result.output
 
         # Ok, now we have all the certificates in certs, walk through it
         # and pull out each certificate and add it to our database
@@ -869,14 +873,15 @@ class CAInstance(DogtagInstance):
 
         # Generate our CSR. The result gets put into stdout
         try:
-            (stdout, _stderr, _returncode) = self.__run_certutil(
+            result = self.__run_certutil(
                 ["-R", "-k", "rsa", "-g", "2048", "-s",
                  str(DN(('CN', 'IPA RA'), self.subject_base)),
-                 "-z", noise_name, "-a"])
+                 "-z", noise_name, "-a"],
+                capture_output=True)
         finally:
             os.remove(noise_name)
 
-        csr = pkcs10.strip_header(stdout)
+        csr = pkcs10.strip_header(result.output)
 
         # Send the request to the CA
         conn = httplib.HTTPConnection(self.fqdn, 8080)
@@ -1051,7 +1056,9 @@ class CAInstance(DogtagInstance):
 
     def publish_ca_cert(self, location):
         args = ["-L", "-n", self.canickname, "-a"]
-        (cert, _err, _returncode) = self.__run_certutil(args)
+        result = self.__run_certutil(
+            args, capture_output=True)
+        cert = result.output
         fd = open(location, "w+")
         fd.write(cert)
         fd.close()
diff --git a/ipaserver/install/certs.py b/ipaserver/install/certs.py
index c918791f0be7a17e20123fe6f94c4ac0bbf09d7b..b5b551a60c08a528ed17381a4ab62350562ca67d 100644
--- a/ipaserver/install/certs.py
+++ b/ipaserver/install/certs.py
@@ -164,8 +164,8 @@ class CertDB(object):
     def gen_password(self):
         return sha1(ipautil.ipa_generate_password()).hexdigest()
 
-    def run_certutil(self, args, stdin=None):
-        return self.nssdb.run_certutil(args, stdin)
+    def run_certutil(self, args, stdin=None, **kwargs):
+        return self.nssdb.run_certutil(args, stdin, **kwargs)
 
     def run_signtool(self, args, stdin=None):
         with open(self.passwd_fname, "r") as f:
@@ -231,8 +231,9 @@ class CertDB(object):
         root_nicknames = self.find_root_cert(nickname)[:-1]
         fd = open(self.cacert_fname, "w")
         for root in root_nicknames:
-            (cert, stderr, returncode) = self.run_certutil(["-L", "-n", root, "-a"])
-            fd.write(cert)
+            result = self.run_certutil(["-L", "-n", root, "-a"],
+                                       capture_output=True)
+            fd.write(result.output)
         fd.close()
         os.chmod(self.cacert_fname, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
         if create_pkcs12:
@@ -276,7 +277,8 @@ class CertDB(object):
         """
         try:
             args = ["-L", "-n", nickname, "-a"]
-            (cert, err, returncode) = self.run_certutil(args)
+            result = self.run_certutil(args, capture_output=True)
+            cert = result.output
             if pem:
                 return cert
             else:
@@ -370,9 +372,10 @@ class CertDB(object):
                 "-z", self.noise_fname,
                 "-f", self.passwd_fname,
                 "-a"]
-        (stdout, stderr, returncode) = self.run_certutil(args)
+        result = self.run_certutil(args,
+                                   capture_output=True, capture_error=True)
         os.remove(self.noise_fname)
-        return (stdout, stderr)
+        return (result.output, result.error_output)
 
     def issue_server_cert(self, certreq_fname, cert_fname):
         self.setup_cert_request()
diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py
index 1b68573d7a64c63d6b0343a1536c90b527f21a05..264f1edbff9dfab4582ef78fb9b72cd3d76a16a6 100644
--- a/ipaserver/install/httpinstance.py
+++ b/ipaserver/install/httpinstance.py
@@ -28,6 +28,9 @@ import re
 import dbus
 import shlex
 import pipes
+import locale
+
+import six
 
 from ipaserver.install import service
 from ipaserver.install import certs
@@ -63,13 +66,17 @@ def httpd_443_configured():
     False otherwise.
     """
     try:
-        (stdout, stderr, rc) = ipautil.run([paths.HTTPD, '-t', '-D', 'DUMP_VHOSTS'])
+        result = ipautil.run([paths.HTTPD, '-t', '-D', 'DUMP_VHOSTS'],
+                             capture_output=True)
     except ipautil.CalledProcessError as e:
         service.print_msg("WARNING: cannot check if port 443 is already configured")
         service.print_msg("httpd returned error when checking: %s" % e)
         return False
 
     port_line_re = re.compile(r'(?P<address>\S+):(?P<port>\d+)')
+    stdout = result.raw_output
+    if six.PY3:
+        stdout = stdout.decode(locale.getpreferredencoding(), errors='replace')
     for line in stdout.splitlines():
         m = port_line_re.match(line)
         if m and int(m.group('port')) == 443:
diff --git a/ipaserver/install/ipa_backup.py b/ipaserver/install/ipa_backup.py
index 6d97ef13b383b9917fa70426a99463f8c14955e8..582d858daddaac69dc6fa4ccc4fdfc6af023f5cb 100644
--- a/ipaserver/install/ipa_backup.py
+++ b/ipaserver/install/ipa_backup.py
@@ -66,7 +66,6 @@ EOF
 """
 
 
-
 def encrypt_file(filename, keyring, remove_original=True):
     source = filename
     dest = filename + '.gpg'
@@ -86,9 +85,9 @@ def encrypt_file(filename, keyring, remove_original=True):
     args.append('-e')
     args.append(source)
 
-    (stdout, stderr, rc) = run(args, raiseonerr=False)
-    if rc != 0:
-        raise admintool.ScriptError('gpg failed: %s' % stderr)
+    result = run(args, raiseonerr=False)
+    if result.returncode != 0:
+        raise admintool.ScriptError('gpg failed: %s' % result.error_log)
 
     if remove_original:
         os.unlink(source)
@@ -420,9 +419,9 @@ class Backup(admintool.AdminTool):
                     '-r',
                     '-n', backend,
                     '-a', ldiffile]
-            (stdout, stderr, rc) = run(args, raiseonerr=False)
-            if rc != 0:
-                self.log.critical("db2ldif failed: %s", stderr)
+            result = run(args, raiseonerr=False)
+            if result.returncode != 0:
+                self.log.critical('db2ldif failed: %s', result.error_log)
 
         # Move the LDIF backup to our location
         shutil.move(ldiffile, os.path.join(self.dir, ldifname))
@@ -464,9 +463,9 @@ class Backup(admintool.AdminTool):
             wait_for_task(conn, dn)
         else:
             args = [paths.DB2BAK, bakdir, '-Z', instance]
-            (stdout, stderr, rc) = run(args, raiseonerr=False)
-            if rc != 0:
-                self.log.critical("db2bak failed: %s" % stderr)
+            result = run(args, raiseonerr=False)
+            if result.returncode != 0:
+                self.log.critical('db2bak failed: %s', result.error_log)
 
         shutil.move(bakdir, self.dir)
 
@@ -493,10 +492,10 @@ class Backup(admintool.AdminTool):
         if options.logs:
             args.extend(verify_directories(self.logs))
 
-        (stdout, stderr, rc) = run(args, raiseonerr=False)
-        if rc != 0:
+        result = run(args, raiseonerr=False)
+        if result.returncode != 0:
             raise admintool.ScriptError('tar returned non-zero code '
-                '%d: %s' % (rc, stderr))
+                '%d: %s' % (result.returncode, result.error_log))
 
         # Backup the necessary directory structure. This is a separate
         # call since we are using the '--no-recursion' flag to store
@@ -514,17 +513,21 @@ class Backup(admintool.AdminTool):
                    ]
             args.extend(missing_directories)
 
-            (stdout, stderr, rc) = run(args, raiseonerr=False)
-            if rc != 0:
-                raise admintool.ScriptError('tar returned non-zero %d when adding '
-                    'directory structure: %s' % (rc, stderr))
+            result = run(args, raiseonerr=False)
+            if result.returncode != 0:
+                raise admintool.ScriptError(
+                    'tar returned non-zero code %d '
+                    'when adding directory structure: %s' % (
+                    result.returncode, result.error_log))
 
         # Compress the archive. This is done separately, since 'tar' cannot
         # append to a compressed archive.
-        (stdout, stderr, rc) = run(['gzip', tarfile], raiseonerr=False)
-        if rc != 0:
-            raise admintool.ScriptError('gzip returned non-zero %d when '
-                'compressing the backup: %s' % (rc, stderr))
+        result = run(['gzip', tarfile], raiseonerr=False)
+        if result.returncode != 0:
+            raise admintool.ScriptError(
+                'gzip returned non-zero code %d '
+                'when compressing the backup: %s' % (
+                result.returncode, result.error_log))
 
         # Rename the archive back to files.tar to preserve compatibility
         os.rename(os.path.join(self.dir, 'files.tar.gz'), tarfile)
@@ -596,9 +599,11 @@ class Backup(admintool.AdminTool):
                 filename,
                 '.'
                ]
-        (stdout, stderr, rc) = run(args, raiseonerr=False)
-        if rc != 0:
-            raise admintool.ScriptError('tar returned non-zero %d: %s' % (rc, stdout))
+        result = run(args, raiseonerr=False)
+        if result.returncode != 0:
+            raise admintool.ScriptError(
+                'tar returned non-zero code %s: %s'% (
+                result.returncode, result.error_log))
 
         if encrypt:
             self.log.info('Encrypting %s' % filename)
diff --git a/ipaserver/install/ipa_restore.py b/ipaserver/install/ipa_restore.py
index a257b7892c3d32bc81cbff1574c2898eeedcda5a..cfa1fdccf26d7d116d1c178f0d12704c642772c0 100644
--- a/ipaserver/install/ipa_restore.py
+++ b/ipaserver/install/ipa_restore.py
@@ -25,8 +25,10 @@ import time
 import pwd
 import ldif
 import itertools
+import locale
 
 from six.moves.configparser import SafeConfigParser
+import six
 
 from ipalib import api, errors, constants
 from ipapython import version, ipautil, certdb
@@ -88,9 +90,9 @@ def decrypt_file(tmpdir, filename, keyring):
     args.append('-d')
     args.append(source)
 
-    (stdout, stderr, rc) = run(args, raiseonerr=False)
-    if rc != 0:
-        raise admintool.ScriptError('gpg failed: %s' % stderr)
+    result = run(args, raiseonerr=False)
+    if result.returncode != 0:
+        raise admintool.ScriptError('gpg failed: %s' % result.error_log)
 
     return dest
 
@@ -365,9 +367,9 @@ class Restore(admintool.AdminTool):
                     dirsrv.start(capture_output=False)
             else:
                 self.log.info('Stopping IPA services')
-                (stdout, stderr, rc) = run(['ipactl', 'stop'], raiseonerr=False)
-                if rc not in [0, 6]:
-                    self.log.warn('Stopping IPA failed: %s' % stderr)
+                result = run(['ipactl', 'stop'], raiseonerr=False)
+                if result.returncode not in [0, 6]:
+                    self.log.warn('Stopping IPA failed: %s' % result.error_log)
 
                 self.restore_selinux_booleans()
 
@@ -573,9 +575,9 @@ class Restore(admintool.AdminTool):
                     '-Z', instance,
                     '-i', ldiffile,
                     '-n', backend]
-            (stdout, stderr, rc) = run(args, raiseonerr=False)
-            if rc != 0:
-                self.log.critical("ldif2db failed: %s" % stderr)
+            result = run(args, raiseonerr=False)
+            if result.returncode != 0:
+                self.log.critical("ldif2db failed: %s" % result.error_log)
 
 
     def bak2db(self, instance, backend, online=True):
@@ -628,9 +630,9 @@ class Restore(admintool.AdminTool):
             if backend is not None:
                 args.append('-n')
                 args.append(backend)
-            (stdout, stderr, rc) = run(args, raiseonerr=False)
-            if rc != 0:
-                self.log.critical("bak2db failed: %s" % stderr)
+            result = run(args, raiseonerr=False)
+            if result.returncode != 0:
+                self.log.critical("bak2db failed: %s" % result.error_log)
 
 
     def restore_default_conf(self):
@@ -650,10 +652,10 @@ class Restore(admintool.AdminTool):
                 paths.IPA_DEFAULT_CONF[1:],
                ]
 
-        (stdout, stderr, rc) = run(args, raiseonerr=False)
-        if rc != 0:
+        result = run(args, raiseonerr=False)
+        if result.returncode != 0:
             self.log.critical('Restoring %s failed: %s' %
-                              (paths.IPA_DEFAULT_CONF, stderr))
+                              (paths.IPA_DEFAULT_CONF, result.error_log))
         os.chdir(cwd)
 
     def remove_old_files(self):
@@ -696,9 +698,9 @@ class Restore(admintool.AdminTool):
             args.append('--exclude')
             args.append('var/log')
 
-        (stdout, stderr, rc) = run(args, raiseonerr=False)
-        if rc != 0:
-            self.log.critical('Restoring files failed: %s', stderr)
+        result = run(args, raiseonerr=False)
+        if result.returncode != 0:
+            self.log.critical('Restoring files failed: %s', result.error_log)
 
         os.chdir(cwd)
 
diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py
index f928e501fe81f3a2acb0f0b16187985025b6930a..921053aba52b2c106ea7bf501cb3c7758fab9e3b 100644
--- a/ipaserver/install/krbinstance.py
+++ b/ipaserver/install/krbinstance.py
@@ -301,9 +301,9 @@ class KrbInstance(service.Service):
         MIN_KRB5KDC_WITH_WORKERS = "1.9"
         cpus = os.sysconf('SC_NPROCESSORS_ONLN')
         workers = False
-        (stdout, stderr, rc) = ipautil.run(['klist', '-V'], raiseonerr=False)
-        if rc == 0:
-            verstr = stdout.split()[-1]
+        result = ipautil.run(['klist', '-V'], raiseonerr=False, capture_output=True)
+        if result.returncode == 0:
+            verstr = result.output.split()[-1]
             ver = version.LooseVersion(verstr)
             min = version.LooseVersion(MIN_KRB5KDC_WITH_WORKERS)
             if ver >= min:
diff --git a/ipaserver/install/opendnssecinstance.py b/ipaserver/install/opendnssecinstance.py
index 4baf6b6bc3d1898aadade8c741f5105b5742d749..533d53afab85ba0ba539cca8e8c4c2a2d423061e 100644
--- a/ipaserver/install/opendnssecinstance.py
+++ b/ipaserver/install/opendnssecinstance.py
@@ -288,10 +288,11 @@ class OpenDNSSECInstance(service.Service):
             # regenerate zonelist.xml
             ods_enforcerd = services.knownservices.ods_enforcerd
             cmd = [paths.ODS_KSMUTIL, 'zonelist', 'export']
-            stdout, stderr, retcode = ipautil.run(cmd,
-                                          runas=ods_enforcerd.get_user_name())
+            result = ipautil.run(cmd,
+                                 runas=ods_enforcerd.get_user_name(),
+                                 capture_output=True)
             with open(paths.OPENDNSSEC_ZONELIST_FILE, 'w') as zonelistf:
-                zonelistf.write(stdout)
+                zonelistf.write(result.output)
                 os.chown(paths.OPENDNSSEC_ZONELIST_FILE,
                          self.ods_uid, self.ods_gid)
                 os.chmod(paths.OPENDNSSEC_ZONELIST_FILE, 0o660)
diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py
index aaa841ca6ac415f90d71a3126f18a787500d7e6a..ec8c5eada0b7edb9a33c9d62884e79e7732f3317 100644
--- a/ipaserver/install/replication.py
+++ b/ipaserver/install/replication.py
@@ -89,10 +89,10 @@ def replica_conn_check(master_host, host_name, realm, check_ca,
 
     if check_ca and dogtag_master_ds_port == 7389:
         args.append('--check-ca')
-    (stdin, stderr, returncode) = ipautil.run(
+    result = ipautil.run(
         args, raiseonerr=False, capture_output=False, nolog=nolog)
 
-    if returncode != 0:
+    if result.returncode != 0:
         sys.exit("Connection check failed!" +
                  "\nPlease fix your network settings according to error messages above." +
                  "\nIf the check results are not valid it can be skipped with --skip-conncheck parameter.")
diff --git a/ipaserver/install/server/install.py b/ipaserver/install/server/install.py
index b6f1b98c544f46608ecb4b9a3267ab733b916617..711d19b260db12d9739d9a4fe3e6211b8168caa8 100644
--- a/ipaserver/install/server/install.py
+++ b/ipaserver/install/server/install.py
@@ -1169,7 +1169,7 @@ def uninstall(installer):
 
     print("Shutting down all IPA services")
     try:
-        (stdout, stderr, rc) = run([paths.IPACTL, "stop"], raiseonerr=False)
+        run([paths.IPACTL, "stop"], raiseonerr=False)
     except Exception as e:
         pass
 
@@ -1254,12 +1254,11 @@ def uninstall(installer):
 
     print("Removing IPA client configuration")
     try:
-        (stdout, stderr, rc) = run([paths.IPA_CLIENT_INSTALL, "--on-master",
-                                    "--unattended", "--uninstall"],
-                                   raiseonerr=False)
-        if rc not in [0, 2]:
-            root_logger.debug("ipa-client-install returned %d" % rc)
-            raise RuntimeError(stdout)
+        result = run([paths.IPA_CLIENT_INSTALL, "--on-master",
+                      "--unattended", "--uninstall"],
+                     raiseonerr=False, capture_output=True)
+        if result.returncode not in [0, 2]:
+            raise RuntimeError(result.output)
     except Exception as e:
         rv = 1
         print("Uninstall of client side components failed!")
diff --git a/ipatests/test_cmdline/test_ipagetkeytab.py b/ipatests/test_cmdline/test_ipagetkeytab.py
index d903305ed280f34d7fe3388103f94a741363995e..2df9b6eb2c82fb7c4bf99ebd5177c575736eb90f 100644
--- a/ipatests/test_cmdline/test_ipagetkeytab.py
+++ b/ipatests/test_cmdline/test_ipagetkeytab.py
@@ -85,8 +85,11 @@ class test_ipagetkeytab(cmdline_test):
                     "-p", "test/notfound.example.com",
                     "-k", self.keytabname,
                    ]
-        (out, err, rc) = ipautil.run(new_args, stdin=None, raiseonerr=False)
+        result = ipautil.run(new_args, stdin=None, raiseonerr=False,
+                             capture_error=True)
+        err = result.error_output
         assert 'Failed to parse result: PrincipalName not found.\n' in err, err
+        rc = result.returncode
         assert rc > 0, rc
 
     def test_2_run(self):
@@ -107,10 +110,11 @@ class test_ipagetkeytab(cmdline_test):
                     "-k", self.keytabname,
                    ]
         try:
-            (out, err, rc) = ipautil.run(new_args, None)
+            result = ipautil.run(new_args, None, capture_error=True)
             expected = 'Keytab successfully retrieved and stored in: %s\n' % (
                 self.keytabname)
-            assert expected in err, 'Success message not in output:\n%s' % err
+            assert (expected in result.error_output,
+                    'Success message not in output:\n%s' % result.error_output)
         except ipautil.CalledProcessError as e:
             assert (False)
 
diff --git a/ipatests/test_ipapython/test_ipautil.py b/ipatests/test_ipapython/test_ipautil.py
index d574bd809ab72a6459fd111e319b7bf4638f7b90..f91b730c5147c36e0b6081df2fc437b3d0056dc9 100644
--- a/ipatests/test_ipapython/test_ipautil.py
+++ b/ipatests/test_ipapython/test_ipautil.py
@@ -1,3 +1,5 @@
+# encoding: utf-8
+
 # Authors:
 #   Jan Cholasta <jchol...@redhat.com>
 #
@@ -417,3 +419,62 @@ class TestTimeParser(object):
         nose.tools.assert_equal(-30, time.tzinfo.minoffset)
         offset = time.tzinfo.utcoffset(time.tzinfo.dst())
         nose.tools.assert_equal(((24 - 9) * 60 * 60) - (30 * 60), offset.seconds)
+
+
+def test_run():
+    result = ipautil.run(['echo', 'foo\x02bar'],
+                         capture_output=True,
+                         capture_error=True)
+    assert result.returncode == 0
+    assert result.output == 'foo\x02bar\n'
+    assert result.raw_output == b'foo\x02bar\n'
+    assert result.error_output == ''
+    assert result.raw_error_output == b''
+
+
+def test_run_no_capture_output():
+    result = ipautil.run(['echo', 'foo\x02bar'])
+    assert result.returncode == 0
+    assert result.output is None
+    assert result.raw_output == b'foo\x02bar\n'
+    assert result.error_output is None
+    assert result.raw_error_output == b''
+
+
+def test_run_bytes():
+    result = ipautil.run(['echo', b'\x01\x02'], capture_output=True)
+    assert result.returncode == 0
+    assert result.output == b'\x01\x02\n'
+
+
+def test_run_decode():
+    result = ipautil.run(['echo', u'á'.encode('utf-8')],
+                         encoding='utf-8', capture_output=True)
+    assert result.returncode == 0
+    if six.PY3:
+        assert result.output == 'á\n'
+    else:
+        assert result.output == 'á\n'.encode('utf-8')
+
+
+def test_run_decode_bad():
+    if six.PY3:
+        with pytest.raises(UnicodeDecodeError):
+            ipautil.run(['echo', b'\xa0\xa1'],
+                        capture_output=True,
+                        encoding='utf-8')
+    else:
+        result = ipautil.run(['echo', '\xa0\xa1'],
+                             capture_output=True,
+                             encoding='utf-8')
+        assert result.returncode == 0
+        assert result.output == '\xa0\xa1\n'
+
+
+def test_backcompat():
+    result = out, err, rc = ipautil.run(['echo', 'foo\x02bar'],
+                                        capture_output=True,
+                                        capture_error=True)
+    assert rc is result.returncode
+    assert out is result.output
+    assert err is result.error_output
diff --git a/ipatests/test_ipapython/test_keyring.py b/ipatests/test_ipapython/test_keyring.py
index 84b9f0ffa4252b4fdeb4302bf4b67212173dd685..79b4b3334a0e42779802ca96be4ce6f9506f6dda 100644
--- a/ipatests/test_ipapython/test_keyring.py
+++ b/ipatests/test_ipapython/test_keyring.py
@@ -28,8 +28,8 @@ import pytest
 pytestmark = pytest.mark.tier0
 
 TEST_KEY = 'ipa_test'
-TEST_VALUE = 'abc123'
-UPDATE_VALUE = '123abc'
+TEST_VALUE = b'abc123'
+UPDATE_VALUE = b'123abc'
 
 SIZE_256 = 'abcdefgh' * 32
 SIZE_512 = 'abcdefgh' * 64
@@ -94,9 +94,10 @@ class test_keyring(object):
 
         # Now update it 10 times
         for i in range(10):
-            kernel_keyring.update_key(TEST_KEY, 'test %d' %  i)
+            value = ('test %d' %  i).encode('ascii')
+            kernel_keyring.update_key(TEST_KEY, value)
             result = kernel_keyring.read_key(TEST_KEY)
-            assert(result == 'test %d' % i)
+            assert(result == value)
 
         kernel_keyring.del_key(TEST_KEY)
 
@@ -134,9 +135,9 @@ class test_keyring(object):
         """
         Test 512-bytes of data
         """
-        kernel_keyring.add_key(TEST_KEY, SIZE_512)
+        kernel_keyring.add_key(TEST_KEY, SIZE_512.encode('ascii'))
         result = kernel_keyring.read_key(TEST_KEY)
-        assert(result == SIZE_512)
+        assert(result == SIZE_512.encode('ascii'))
 
         kernel_keyring.del_key(TEST_KEY)
 
@@ -144,8 +145,8 @@ class test_keyring(object):
         """
         Test 1k bytes of data
         """
-        kernel_keyring.add_key(TEST_KEY, SIZE_1024)
+        kernel_keyring.add_key(TEST_KEY, SIZE_1024.encode('ascii'))
         result = kernel_keyring.read_key(TEST_KEY)
-        assert(result == SIZE_1024)
+        assert(result == SIZE_1024.encode('ascii'))
 
         kernel_keyring.del_key(TEST_KEY)
diff --git a/ipatests/test_xmlrpc/test_host_plugin.py b/ipatests/test_xmlrpc/test_host_plugin.py
index be2b2d6af15933c9a4d56ede1ec8e5455637536f..47f05a403ddb519f201b11251c2acb71faa9133b 100644
--- a/ipatests/test_xmlrpc/test_host_plugin.py
+++ b/ipatests/test_xmlrpc/test_host_plugin.py
@@ -552,7 +552,7 @@ class TestHostFalsePwdChange(XMLRPC_test):
         ]
 
         try:
-            out, err, rc = ipautil.run(new_args)
+            ipautil.run(new_args)
         except ipautil.CalledProcessError as e:
             # join operation may fail on 'adding key into keytab', but
             # the keytab is not necessary for further tests
-- 
2.5.0

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to