On Fri, 2012-10-19 at 14:15 -0400, Rob Crittenden wrote:
> Simo Sorce wrote:
> > On Fri, 2012-10-19 at 13:47 -0400, Rob Crittenden wrote:
> >> Simo Sorce wrote:
> >>> On Thu, 2012-10-18 at 11:51 -0400, Rob Crittenden wrote:
> >>>> Simo Sorce wrote:
> >>>>> On Thu, 2012-10-18 at 11:37 -0400, Rob Crittenden wrote:
> >>>>>> Simo Sorce wrote:
> >>>>>>> Also improve shutdown reliability and restart behavior so we always 
> >>>>>>> kill
> >>>>>>> all the processes we started even if the list of processes to handle
> >>>>>>> changed in LDAP.
> >>>>>>>
> >>>>>>> Fixes: https://fedorahosted.org/freeipa/ticket/2302
> >>>>>>
> >>>>>> Should this list be updated if we do a post-install of DNS or the CA? 
> >>>>>> It
> >>>>>> isn't now which would leave some services running.
> >>>>>
> >>>>> Yes we probably should,
> >>>>> but I think it should be done as a separate ticket.
> >>>>>
> >>>>> Simo.
> >>>>>
> >>>>
> >>>> What's the impact if we don't do this now?
> >>>
> >>> some services will not be turned off by ipactl, however this shouldn't
> >>> impact much at shutdown, as systemd will shutdown left over stuff as it
> >>> keeps track of all processes.
> >>>
> >>> uhmm however if someone does a ipactl restart those services will not be
> >>> restarted the first time it is run (we will tell systemd to start them
> >>> but it will do likely nothing as they are already started), after that
> >>> they will be handled as they fact they are enabled will be stored in the
> >>> file.
> >>
> >> Well, what is the downside of a call to refresh the list of running
> >> services that should be run at the end of setting up new services?
> >>
> >> Otherwise we could find outselves with a bunch of unreproducible cases
> >> where things weren't start/stopped properly.
> >
> > Yeah I have been thinking about that.
> > I am thinking of changing the patch so that it's the service class
> > itself that saves this data.
> >
> > That way saving is always performed no matter 'what' starts the service.
> > Do you agree ?
> >
> 
> Yes, that sounds like it'll be very robust.

Ok I created 3 new patches that replace the previous one.

The first 2 patches lay groundwork to have the service class save in the
services.list file.
The last one changes ipactl to actually use the file.

Simo.


-- 
Simo Sorce * Red Hat, Inc * New York
>From ef39621897d3bced7f4bf41ab9ac04a266bf9fa3 Mon Sep 17 00:00:00 2001
From: Simo Sorce <sso...@redhat.com>
Date: Mon, 22 Oct 2012 16:59:43 -0400
Subject: [PATCH 1/3] Preserve original service_name in services

This is needed to be able to reference stuff always wth the same name.
The platform specific private name must be kept in a platform specific
variable.
In the case of systemd we store it in systemd_name
For the redhat platform wellknown names and service name are the same so
currently no special name is needed.
---
 ipapython/platform/fedora16.py | 11 +++++------
 ipapython/platform/systemd.py  | 19 ++++++++++---------
 2 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/ipapython/platform/fedora16.py b/ipapython/platform/fedora16.py
index 794c39e2091f9402282e18fbe162d40892cb1e0d..ced85ce593dd31655dcbbd8a6d1900cee7b9e9ea 100644
--- a/ipapython/platform/fedora16.py
+++ b/ipapython/platform/fedora16.py
@@ -68,15 +68,14 @@ system_units['pki_tomcatd'] = system_units['pki-tomcatd']
 class Fedora16Service(systemd.SystemdService):
     def __init__(self, service_name):
         if service_name in system_units:
-            service_name = system_units[service_name]
+            systemd_name = system_units[service_name]
         else:
             if len(service_name.split('.')) == 1:
                 # if service_name does not have a dot, it is not foo.service
                 # and not a foo.target. Thus, not correct service name for
                 # systemd, default to foo.service style then
-                service_name = "%s.service" % (service_name)
-        super(Fedora16Service, self).__init__(service_name)
-
+                systemd_name = "%s.service" % (service_name)
+        super(Fedora16Service, self).__init__(service_name, systemd_name)
 # Special handling of directory server service
 #
 # We need to explicitly enable instances to install proper symlinks as
@@ -104,8 +103,8 @@ class Fedora16DirectoryService(Fedora16Service):
 
     def restart(self, instance_name="", capture_output=True, wait=True):
         if len(instance_name) > 0:
-            elements = self.service_name.split("@")
-            srv_etc = os.path.join(self.SYSTEMD_ETC_PATH, self.service_name)
+            elements = self.systemd_name.split("@")
+            srv_etc = os.path.join(self.SYSTEMD_ETC_PATH, self.systemd_name)
             srv_tgt = os.path.join(self.SYSTEMD_ETC_PATH, self.SYSTEMD_SRV_TARGET % (elements[0]))
             srv_lnk = os.path.join(srv_tgt, self.service_instance(instance_name))
             if not os.path.exists(srv_etc):
diff --git a/ipapython/platform/systemd.py b/ipapython/platform/systemd.py
index c174488c08a73ce02b5f568ddd24c98d8dab83d1..6c25a79b6ecdfbda1c85ada6642a656d704fdb2d 100644
--- a/ipapython/platform/systemd.py
+++ b/ipapython/platform/systemd.py
@@ -27,25 +27,26 @@ class SystemdService(base.PlatformService):
     SYSTEMD_LIB_PATH = "/lib/systemd/system/"
     SYSTEMD_SRV_TARGET = "%s.target.wants"
 
-    def __init__(self, service_name):
+    def __init__(self, service_name, systemd_name):
         super(SystemdService, self).__init__(service_name)
-        self.lib_path = os.path.join(self.SYSTEMD_LIB_PATH, self.service_name)
+        self.systemd_name = systemd_name
+        self.lib_path = os.path.join(self.SYSTEMD_LIB_PATH, self.systemd_name)
         self.lib_path_exists = None
 
     def service_instance(self, instance_name):
         if self.lib_path_exists is None:
             self.lib_path_exists = os.path.exists(self.lib_path)
 
-        elements = self.service_name.split("@")
+        elements = self.systemd_name.split("@")
 
         # Short-cut: if there is already exact service name, return it
         if self.lib_path_exists and len(instance_name) == 0:
             if len(elements) == 1:
                 # service name is like pki-tomcatd.target or krb5kdc.service
-                return self.service_name
+                return self.systemd_name
             if len(elements) > 1 and elements[1][0] != '.':
                 # Service name is like pki-tomcatd@pki-tomcat.service and that file exists
-                return self.service_name
+                return self.systemd_name
 
         if len(elements) > 1:
             # We have dynamic service
@@ -59,7 +60,7 @@ class SystemdService(base.PlatformService):
                 if os.path.exists(srv_lib):
                     return tgt_name
 
-        return self.service_name
+        return self.systemd_name
 
     def parse_variables(self, text, separator=None):
         """
@@ -82,7 +83,7 @@ class SystemdService(base.PlatformService):
         if instance_name in base.wellknownports:
             ports = base.wellknownports[instance_name]
         else:
-            elements = self.service_name.split("@")
+            elements = self.systemd_name.split("@")
             if elements[0] in base.wellknownports:
                 ports = base.wellknownports[elements[0]]
         if ports:
@@ -141,7 +142,7 @@ class SystemdService(base.PlatformService):
     def enable(self, instance_name=""):
         if self.lib_path_exists is None:
             self.lib_path_exists = os.path.exists(self.lib_path)
-        elements = self.service_name.split("@")
+        elements = self.systemd_name.split("@")
         l = len(elements)
 
         if self.lib_path_exists and (l > 1 and elements[1][0] != '.'):
@@ -183,7 +184,7 @@ class SystemdService(base.PlatformService):
             self.__enable(instance_name)
 
     def disable(self, instance_name=""):
-        elements = self.service_name.split("@")
+        elements = self.systemd_name.split("@")
         if instance_name != "" and len(elements) > 1:
             # Remove instance, we need to do following:
             #  Remove link from /etc/systemd/system/<service>.target.wants/<service>@<instance_name>.service
-- 
1.7.11.4

>From c1c7726b2f8fc168eb4f0e8965460a51b86b9f18 Mon Sep 17 00:00:00 2001
From: Simo Sorce <sso...@redhat.com>
Date: Mon, 22 Oct 2012 17:01:58 -0400
Subject: [PATCH 2/3] Save service name on service startup

This is done as a default action of the ancestor class so that no matter what
platform is currently used this code is always the same and the name is the
wellknown service name.
This information will be used by ipacl to stop only and all the services that
have been started by any ipa tool/install script
---
 ipapython/platform/base.py    | 23 +++++++++++++++++++++++
 ipapython/platform/redhat.py  |  1 +
 ipapython/platform/systemd.py |  1 +
 ipapython/services.py.in      |  4 ++++
 4 files changed, 29 insertions(+)

diff --git a/ipapython/platform/base.py b/ipapython/platform/base.py
index 2d39d216991c08c4d439a34af99b67b250058889..1101b5cdcf1d033f757598ad5b0fdc526ca8eefe 100644
--- a/ipapython/platform/base.py
+++ b/ipapython/platform/base.py
@@ -17,6 +17,8 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 from ipalib.plugable import MagicDict
+import json
+import os
 
 # Canonical names of services as IPA wants to see them. As we need to have
 # *some* naming, set them as in Red Hat distributions. Actual implementation
@@ -40,6 +42,8 @@ wellknownports = {
     'pki-tomcatd': [8080, 8443],  # used if the incoming instance name is blank
 }
 
+SVC_LIST_FILE = "/var/run/ipa/services.list"
+
 class AuthConfig(object):
     """
     AuthConfig class implements system-independent interface to configure
@@ -133,6 +137,25 @@ class PlatformService(object):
         self.service_name = service_name
 
     def start(self, instance_name="", capture_output=True, wait=True):
+        """
+        When a service is started record the fact in a special file.
+        This allows ipactl stop to always stop all services that have
+        been started via ipa tools
+        """
+        svc_list = []
+        try:
+            f = open(SVC_LIST_FILE, 'r')
+            svc_list = json.load(f)
+        except Exception:
+            # not fatal, may be the first service
+            pass
+
+        svc_list.append(self.service_name)
+
+        f = open(SVC_LIST_FILE, 'w')
+        json.dump(svc_list, f)
+        f.flush()
+        f.close()
         return
 
     def stop(self, instance_name="", capture_output=True):
diff --git a/ipapython/platform/redhat.py b/ipapython/platform/redhat.py
index 3551c28410ceeabfc1064ac79e86dc7ee40dd8c3..cd1277733e8c8798b5ef540d4b14c0b1b9d96088 100644
--- a/ipapython/platform/redhat.py
+++ b/ipapython/platform/redhat.py
@@ -71,6 +71,7 @@ class RedHatService(base.PlatformService):
         ipautil.run(["/sbin/service", self.service_name, "start", instance_name], capture_output=capture_output)
         if wait and self.is_running(instance_name):
             self.__wait_for_open_ports(instance_name)
+        super(RedHatService, self).start(instance_name)
 
     def restart(self, instance_name="", capture_output=True, wait=True):
         ipautil.run(["/sbin/service", self.service_name, "restart", instance_name], capture_output=capture_output)
diff --git a/ipapython/platform/systemd.py b/ipapython/platform/systemd.py
index 6c25a79b6ecdfbda1c85ada6642a656d704fdb2d..63414058355e7697c04126ede7dbb77f73e94cab 100644
--- a/ipapython/platform/systemd.py
+++ b/ipapython/platform/systemd.py
@@ -96,6 +96,7 @@ class SystemdService(base.PlatformService):
         ipautil.run(["/bin/systemctl", "start", self.service_instance(instance_name)], capture_output=capture_output)
         if wait and self.is_running(instance_name):
             self.__wait_for_open_ports(self.service_instance(instance_name))
+        super(SystemdService, self).start(instance_name)
 
     def restart(self, instance_name="", capture_output=True, wait=True):
         # Restart command is broken before systemd-36-3.fc16
diff --git a/ipapython/services.py.in b/ipapython/services.py.in
index 8fe663763d06d89c62604086a3eea7a9a983f49d..16b62ca8508d4078e896cd1da6fd664f52a3930e 100644
--- a/ipapython/services.py.in
+++ b/ipapython/services.py.in
@@ -51,4 +51,8 @@ backup_and_replace_hostname = backup_and_replace_hostname_default
 def check_selinux_status():
     return
 
+from ipapython.platform.base import SVC_LIST_FILE
+def get_svc_list_file():
+    return SVC_LIST_FILE
+
 from ipapython.platform.SUPPORTED_PLATFORM import *
-- 
1.7.11.4

>From 763dcede8cbbe1a2f76e9940f15803792cf03aec Mon Sep 17 00:00:00 2001
From: Simo Sorce <sso...@redhat.com>
Date: Fri, 12 Oct 2012 15:58:02 -0400
Subject: [PATCH 3/3] Get list of service from LDAP only at startup

We check (possibly different) data from LDAP only at (re)start.
This way we always shutdown exactly the services we started even if the list
changed in the meanwhile (we avoid leaving a service running even if it was
removed from LDAP as the admin decided it should not be started in future).

This should also fix a problematic deadlock with systemd when we try to read
the list of service from LDAP at shutdown.
---
 freeipa.spec.in                |   2 +
 init/systemd/ipa.conf.tmpfiles |   1 +
 install/tools/ipactl           | 195 ++++++++++++++++++++++++++++-------------
 3 files changed, 139 insertions(+), 59 deletions(-)

diff --git a/freeipa.spec.in b/freeipa.spec.in
index 916630029f6dfac8ef32dabb00f338052cbbf08e..41745c318655fa3eb37a512aaf253016f1620581 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -446,6 +446,7 @@ install -m 0644 init/systemd/ipa.conf.tmpfiles %{buildroot}%{_sysconfdir}/tmpfil
 
 mkdir -p %{buildroot}%{_localstatedir}/run/
 install -d -m 0700 %{buildroot}%{_localstatedir}/run/ipa_memcached/
+install -d -m 0700 %{buildroot}%{_localstatedir}/run/ipa/
 
 mkdir -p %{buildroot}%{_libdir}/krb5/plugins/libkrb5
 touch %{buildroot}%{_libdir}/krb5/plugins/libkrb5/winbind_krb5_locator.so
@@ -623,6 +624,7 @@ fi
 %{_sysconfdir}/cron.d/ipa-compliance
 %config(noreplace) %{_sysconfdir}/sysconfig/ipa_memcached
 %dir %attr(0700,apache,apache) %{_localstatedir}/run/ipa_memcached/
+%dir %attr(0700,root,root) %{_localstatedir}/run/ipa/
 %if 0%{?fedora} >= 15
 %config %{_sysconfdir}/tmpfiles.d/ipa.conf
 %endif
diff --git a/init/systemd/ipa.conf.tmpfiles b/init/systemd/ipa.conf.tmpfiles
index e4b679a55d68a6b83991ac72dd520c32b2a0de50..1e7a896ed8df00c97f2d092504e2a65960bb341d 100644
--- a/init/systemd/ipa.conf.tmpfiles
+++ b/init/systemd/ipa.conf.tmpfiles
@@ -1 +1,2 @@
 d /var/run/ipa_memcached 0700 apache apache
+d /var/run/ipa 0700 root root
diff --git a/install/tools/ipactl b/install/tools/ipactl
index d4b2c0878f2b62fd12198f76bef01ef70e9f3de1..18e42dd7d72ea2ae2ac090d5b288a33e9fc41f6d 100755
--- a/install/tools/ipactl
+++ b/install/tools/ipactl
@@ -34,6 +34,7 @@ try:
     import ldap.sasl
     import ldapurl
     import socket
+    import json
 except ImportError:
     print >> sys.stderr, """\
 There was a problem importing one of the required Python modules. The
@@ -162,7 +163,22 @@ def get_config(dirsrv):
         for p in entry[1]['ipaConfigString']:
             if p.startswith('startOrder '):
                 order = p.split()[1]
-        svc_list.append((order, name))
+        svc_list.append([order, name])
+
+    ordered_list = []
+    for (order, svc) in sorted(svc_list):
+        ordered_list.append(service.SERVICE_LIST[svc][0])
+    return ordered_list
+
+def get_config_from_file():
+
+    svc_list = []
+
+    try:
+        f = open(ipaservices.get_svc_list_file(), 'r')
+        svc_list = json.load(f)
+    except Exception, e:
+        raise IpactlError("Unknown error when retrieving list of services from file: " + str(e))
 
     return svc_list
 
@@ -174,7 +190,7 @@ def ipa_start(options):
     except Exception, e:
         raise IpactlError("Failed to start Directory Service: " + str(e))
 
-    svc_list = []
+    ldap_list = []
     try:
         svc_list = get_config(dirsrv)
     except Exception, e:
@@ -191,21 +207,22 @@ def ipa_start(options):
             raise IpactlError()
 
     if len(svc_list) == 0:
-        # no service to stop
+        # no service to start
         return
 
-    for (order, svc) in sorted(svc_list):
-        svc_name = service.SERVICE_LIST[svc][0]
-        svchandle = ipaservices.service(svc_name)
+    # remove possibly stale file with list of started services
+    os.unlink(ipaservices.SVC_LIST_FILE)
+
+    for svc in svc_list:
+        svchandle = ipaservices.service(svc)
         try:
             print "Starting %s Service" % svc
-            svchandle.start(capture_output=get_capture_output(svc_name, options.debug))
+            svchandle.start(capture_output=get_capture_output(svc, options.debug))
         except:
             emit_err("Failed to start %s Service" % svc)
             emit_err("Shutting down")
-            for (order, svc) in sorted(svc_list):
-                svc_name = service.SERVICE_LIST[svc][0]
-                svc_off = ipaservices.service(svc_name)
+            for svc in svc_list:
+                svc_off = ipaservices.service(svc)
                 try:
                     svc_off.stop(capture_output=False)
                 except:
@@ -220,11 +237,10 @@ def ipa_stop(options):
     dirsrv = ipaservices.knownservices.dirsrv
     svc_list = []
     try:
-        svc_list = get_config(dirsrv)
+        svc_list = get_config_from_file()
     except Exception, e:
-        # ok if dirsrv died this may fail, so let's try to quickly restart it
-        # and see if we can get anything. If not throw our hands up and just
-        # exit
+        # Issue reading the file ? Let's try to get data from LDAP as a
+        # fallback
         try:
             dirsrv.start(capture_output=False)
             svc_list = get_config(dirsrv)
@@ -241,9 +257,8 @@ def ipa_stop(options):
         # no service to stop
         return
 
-    for (order, svc) in sorted(svc_list, reverse=True):
-        svc_name = service.SERVICE_LIST[svc][0]
-        svchandle = ipaservices.service(svc_name)
+    for svc in reversed(svc_list):
+        svchandle = ipaservices.service(svc)
         try:
             print "Stopping %s Service" % svc
             svchandle.stop(capture_output=False)
@@ -259,55 +274,126 @@ def ipa_stop(options):
 
 def ipa_restart(options):
     dirsrv = ipaservices.knownservices.dirsrv
+    new_svc_list = []
+    try:
+        new_svc_list = get_config(dirsrv)
+    except Exception, e:
+        emit_err("Failed to read data from Directory Service: " + str(e))
+        emit_err("Shutting down")
+        try:
+            dirsrv.stop(capture_output=False)
+        except:
+            pass
+        if isinstance(e, IpactlError):
+            # do not display any other error message
+            raise IpactlError(rval=e.rval)
+        else:
+            raise IpactlError()
+
+    old_svc_list = []
+    try:
+        old_svc_list = get_config_from_file()
+    except Exception, e:
+        emit_err("Failed to get service list from file: " + str(e))
+        # fallback to what's in LDAP
+        old_svc_list = new_svc_list
+
+    # match service to start/stop
+    svc_list = []
+    for s in new_svc_list:
+        if s in old_svc_list:
+            svc_list.append(s)
+
+    #remove commons
+    for s in svc_list:
+        if s in old_svc_list:
+            old_svc_list.remove(s)
+    for s in svc_list:
+        if s in new_svc_list:
+            new_svc_list.remove(s)
+
+    if len(old_svc_list) != 0:
+        # we need to definitely stop some services
+        for svc in reversed(old_svc_list):
+            svchandle = ipaservices.service(svc)
+            try:
+                print "Stopping %s Service" % svc
+                svchandle.stop(capture_output=False)
+            except:
+                emit_err("Failed to stop %s Service" % svc)
+
     try:
         print "Restarting Directory Service"
         dirsrv.restart(capture_output=get_capture_output('dirsrv', options.debug))
     except Exception, e:
-        raise IpactlError("Failed to restart Directory Service: " + str(e))
-
-    svc_list = []
-    try:
-        svc_list = get_config(dirsrv)
-    except Exception, e:
-        emit_err("Failed to read data from Directory Service: " + str(e))
+        emit_err("Failed to restart Directory Service: " + str(e))
         emit_err("Shutting down")
+        for svc in reversed(svc_list):
+            svc_off = ipaservices.service(svc)
+            try:
+                svc_off.stop(capture_output=False)
+            except:
+                pass
         try:
             dirsrv.stop(capture_output=False)
         except:
             pass
-        if isinstance(e, IpactlError):
-            # do not display any other error message
-            raise IpactlError(rval=e.rval)
-        else:
-            raise IpactlError()
+        raise IpactlError("Aborting ipactl")
 
-    if len(svc_list) == 0:
-        # no service to stop
-        return
+    if len(svc_list) != 0:
+        # there are services to restart
 
-    for (order, svc) in sorted(svc_list):
-        svc_name = service.SERVICE_LIST[svc][0]
-        svchandle = ipaservices.service(svc_name)
-        try:
-            print "Restarting %s Service" % svc
-            svchandle.restart(capture_output=get_capture_output(svc_name, options.debug))
-        except:
-            emit_err("Failed to restart %s Service" % svc)
-            emit_err("Shutting down")
-            for (order, svc) in sorted(svc_list):
-                svc_name = service.SERVICE_LIST[svc][0]
-                svc_off = ipaservices.service(svc_name)
+        for svc in svc_list:
+            svchandle = ipaservices.service(svc)
+            try:
+                print "Restarting %s Service" % svc
+                svchandle.restart(capture_output=get_capture_output(svc, options.debug))
+            except:
+                emit_err("Failed to restart %s Service" % svc)
+                emit_err("Shutting down")
+                for svc in reversed(svc_list):
+                    svc_off = ipaservices.service(svc)
+                    try:
+                        svc_off.stop(capture_output=False)
+                    except:
+                        pass
                 try:
-                    svc_off.stop(capture_output=False)
+                    dirsrv.stop(capture_output=False)
                 except:
                     pass
+                raise IpactlError("Aborting ipactl")
+
+    if len(new_svc_list) != 0:
+        # we still need to start some services
+        for svc in new_svc_list:
+            svchandle = ipaservices.service(svc)
             try:
-                dirsrv.stop(capture_output=False)
+                print "Starting %s Service" % svc
+                svchandle.start(capture_output=get_capture_output(svc, options.debug))
             except:
-                pass
-            raise IpactlError("Aborting ipactl")
+                emit_err("Failed to start %s Service" % svc)
+                emit_err("Shutting down")
+                for svc in reversed(svc_list):
+                    svc_off = ipaservices.service(svc)
+                    try:
+                        svc_off.stop(capture_output=False)
+                    except:
+                        pass
+                try:
+                    dirsrv.stop(capture_output=False)
+                except:
+                    pass
+                raise IpactlError("Aborting ipactl")
 
 def ipa_status(options):
+
+    try:
+        svc_list = get_config_from_file()
+    except IpactlError, e:
+        raise e
+    except Exception, e:
+        raise IpactlError("Failed to get list of services to probe status: " + str(e))
+
     dirsrv = ipaservices.knownservices.dirsrv
     try:
         if dirsrv.is_running():
@@ -317,20 +403,11 @@ def ipa_status(options):
     except:
         raise IpactlError("Failed to get Directory Service status")
 
-    svc_list = []
-    try:
-        svc_list = get_config(dirsrv)
-    except IpactlError, e:
-        raise e
-    except Exception, e:
-        raise IpactlError("Failed to get list of services to probe status: " + str(e))
-
     if len(svc_list) == 0:
         return
 
-    for (order, svc) in sorted(svc_list):
-        svc_name = service.SERVICE_LIST[svc][0]
-        svchandle = ipaservices.service(svc_name)
+    for svc in svc_list:
+        svchandle = ipaservices.service(svc)
         try:
             if svchandle.is_running():
                 print "%s Service: RUNNING" % svc
-- 
1.7.11.4

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to