On 10/19/2012 05:42 PM, Rob Crittenden wrote:
Petr Viktorin wrote:
https://fedorahosted.org/freeipa/ticket/3084
See ticket & commit message.


Please tell me of a better way to extend the Services.


What's interesting is that usually the CA is "running" right after the
ports are opened, but if not, it takes *exactly* one minute between the
ports being open and the time I stop getting 503 "Service Temporarily
Unavailable" from ca/admin/ca/getStatus. Is there a sleep somewhere in
pki? or httpd? or IPΑ?

No sleep that I know of, and I'm not seeing that behavior. In my testing
I got 503 exactly once. Most of the time once the port(s) were open and
the request went through the status was that dogtag was up and ready.

Just a few minor requests.

Can you add a block comment to ca_status? I think particularly
explaining why port 443 and not a CA port directly (I assume so we test
the proxy).

Added.


I'm a little confused by the wait variable. It is a boolean in some
cases and a string in others (no-proxy)? Why not just pass in False?

Since the proxy is installed after the CA, we can't check the proxy in CA installation's restart step, but we do want to wait for the ports to open. I agree the 'no-proxy' was a bad solution. The included patch just skips the check if the httpd service is not configured yet.

I found out that knownservices.httpd.is_installed() returns True even after an IPA server is uninstalled. I'm not sure if that's expected, I've sent a separate mail asking for clarification. As a workaround I check for the /etc/httpd/conf.d/ipa.conf file, which we remove on uninstall.

Juggling the proxy configuration, httpd restart, and CA restart/wait to happen in the right order in all cases is not trivial (especially with the long testing times). If you can think of an exotic scenario, please test it.

The patch itself looks good. I'm having a replica install problem which
I'm guessing is unrelated.

The configure proxy step is failing to restart httpd. It is failing
because the default mod_nss port is 8443 which is also being used by
dogtag, so httpd fails to restart and the installation blows up.

Thanks for catching this. It was happening with --setup-ca. Fixed.

I think that ipa-replica-install --setup-ca should be changed to behave exactly like ipa-replica-install followed by ipa-ca-install. Same for the DNS installation. If there are no objections I'll include this in my installer framework effort (#2652).


--
Petr³

From 348e963028ccccdcda6deb2c1eb9ba84f5182c4a Mon Sep 17 00:00:00 2001
From: Petr Viktorin <pvikt...@redhat.com>
Date: Tue, 25 Sep 2012 09:57:03 -0400
Subject: [PATCH] Make sure the CA is running when starting services

- Provide a function for determinig the CA status using Dogtag 10's new
  getStatus endpoint.
  This must be done over HTTPS, but since our client certificate may not be set
  up yet, we need HTTPS without client authentication.
  Rather than copying from the existing http_request and https_request
  function, shared code is factored out to a common helper.
- Call the new function when restarting the CA service. Since our Service
  can only be extended in platform-specific code, do this for Fedora only.
  Also, the status is only checked with Dogtag 10+.
- When a restart call in cainstance failed, users were refered to the
  installation log, but no info was actually logged. Log the exception.

https://fedorahosted.org/freeipa/ticket/3084
---
 ipapython/dogtag.py             | 164 +++++++++++++++++++++++++---------------
 ipapython/platform/fedora16.py  |  51 ++++++++++++-
 ipaserver/install/cainstance.py |   8 ++
 3 files changed, 162 insertions(+), 61 deletions(-)

diff --git a/ipapython/dogtag.py b/ipapython/dogtag.py
index 907cebc613127288554378d516eb456f421455ba..5cf5a9df89fa70533d81a78fb18b0ee0f52d6f87 100644
--- a/ipapython/dogtag.py
+++ b/ipapython/dogtag.py
@@ -130,6 +130,15 @@ def configured_constants(api=None):
         return Dogtag9Constants
 
 
+def error_from_xml(doc, message_template):
+    try:
+        item_node = doc.getElementsByTagName("Error")
+        reason = item_node[0].childNodes[0].data
+        return errors.RemoteRetrieveError(reason=reason)
+    except Exception, e:
+        return errors.RemoteRetrieveError(reason=message_template % e)
+
+
 def get_ca_certchain(ca_host=None):
     """
     Retrieve the CA Certificate chain from the configured Dogtag server.
@@ -151,48 +160,118 @@ def get_ca_certchain(ca_host=None):
                 item_node = doc.getElementsByTagName("ChainBase64")
                 chain = item_node[0].childNodes[0].data
             except IndexError:
-                try:
-                    item_node = doc.getElementsByTagName("Error")
-                    reason = item_node[0].childNodes[0].data
-                    raise errors.RemoteRetrieveError(reason=reason)
-                except Exception, e:
-                    raise errors.RemoteRetrieveError(
-                        reason=_("Retrieving CA cert chain failed: %s") % e)
+                raise error_from_xml(
+                    doc, _("Retrieving CA cert chain failed: %s"))
         finally:
             if doc:
                 doc.unlink()
     else:
         raise errors.RemoteRetrieveError(
             reason=_("request failed with HTTP status %d") % res.status)
 
     return chain
 
+
+def ca_status(ca_host=None):
+    """Return the status of the CA, and the httpd proxy in front of it
+
+    The returned status can be:
+    - running
+    - starting
+    - Service Temporarily Unavailable
+    """
+    if ca_host is None:
+        ca_host = api.env.ca_host
+    # Use port 443 to test the proxy as well
+    status, reason, headers, body = unauthenticated_https_request(
+        ca_host, 443, '/ca/admin/ca/getStatus')
+    if status == 503:
+        # Service temporarily unavailable
+        return reason
+    elif status != 200:
+        raise errors.RemoteRetrieveError(
+            reason=_("Retrieving CA status failed: %s") % reason)
+    doc = xml.dom.minidom.parseString(body)
+    try:
+        item_node = doc.getElementsByTagName("XMLResponse")[0]
+        item_node = item_node.getElementsByTagName("Status")[0]
+        return item_node.childNodes[0].data
+    except IndexError:
+        raise error_from_xml(doc, _("Retrieving CA status failed: %s"))
+
+
 def https_request(host, port, url, secdir, password, nickname, **kw):
     """
-    :param url: The URL to post to.
+    :param url: The path (not complete URL!) to post to.
     :param kw:  Keyword arguments to encode into POST body.
     :return:   (http_status, http_reason_phrase, http_headers, http_body)
                as (integer, unicode, dict, str)
 
     Perform a client authenticated HTTPS request
     """
-    if isinstance(host, unicode):
-        host = host.encode('utf-8')
-    uri = 'https://%s%s' % (ipautil.format_netloc(host, port), url)
-    post = urlencode(kw)
-    root_logger.debug('https_request %r', uri)
-    root_logger.debug('https_request post %r', post)
-    request_headers = {"Content-type": "application/x-www-form-urlencoded",
-                       "Accept": "text/plain"}
-    try:
+
+    def connection_factory(host, port):
         conn = nsslib.NSSConnection(host, port, dbdir=secdir)
         conn.set_debuglevel(0)
         conn.connect()
-        conn.sock.set_client_auth_data_callback(nsslib.client_auth_data_callback,
-                                                nickname,
-                                                password, nss.get_default_certdb())
-        conn.request("POST", url, post, request_headers)
+        conn.sock.set_client_auth_data_callback(
+            nsslib.client_auth_data_callback,
+            nickname, password, nss.get_default_certdb())
+        return conn
 
+    body = urlencode(kw)
+    return _httplib_request(
+            'https', host, port, url, connection_factory, body)
+
+
+def http_request(host, port, url, **kw):
+    """
+    :param url: The path (not complete URL!) to post to.
+    :param kw: Keyword arguments to encode into POST body.
+    :return:   (http_status, http_reason_phrase, http_headers, http_body)
+                as (integer, unicode, dict, str)
+
+    Perform an HTTP request.
+    """
+    body = urlencode(kw)
+    return _httplib_request(
+        'http', host, port, url, httplib.HTTPConnection, body)
+
+
+def unauthenticated_https_request(host, port, url, **kw):
+    """
+    :param url: The path (not complete URL!) to post to.
+    :param kw: Keyword arguments to encode into POST body.
+    :return:   (http_status, http_reason_phrase, http_headers, http_body)
+                as (integer, unicode, dict, str)
+
+    Perform an unauthenticated HTTPS request.
+    """
+    body = urlencode(kw)
+    return _httplib_request(
+        'https', host, port, url, httplib.HTTPSConnection, body)
+
+
+def _httplib_request(
+        protocol, host, port, path, connection_factory, request_body):
+    """
+    :param request_body: Request body
+    :param connection_factory: Connection class to use. Will be called
+        with the host and port arguments.
+
+    Perform a HTTP(s) request.
+    """
+    if isinstance(host, unicode):
+        host = host.encode('utf-8')
+    uri = '%s://%s%s' % (protocol, ipautil.format_netloc(host, port), path)
+    root_logger.info('request %r', uri)
+    root_logger.debug('request body %r', request_body)
+    try:
+        conn = connection_factory(host, port)
+        conn.request('POST', uri,
+            body=request_body,
+            headers={'Content-type': 'application/x-www-form-urlencoded'},
+        )
         res = conn.getresponse()
 
         http_status = res.status
@@ -203,42 +282,9 @@ def https_request(host, port, url, secdir, password, nickname, **kw):
     except Exception, e:
         raise NetworkError(uri=uri, error=str(e))
 
+    root_logger.debug('request status %d',        http_status)
+    root_logger.debug('request reason_phrase %r', http_reason_phrase)
+    root_logger.debug('request headers %s',       http_headers)
+    root_logger.debug('request body %r',          http_body)
+
     return http_status, http_reason_phrase, http_headers, http_body
-
-def http_request(host, port, url, **kw):
-        """
-        :param url: The URL to post to.
-        :param kw: Keyword arguments to encode into POST body.
-        :return:   (http_status, http_reason_phrase, http_headers, http_body)
-                   as (integer, unicode, dict, str)
-
-        Perform an HTTP request.
-        """
-        if isinstance(host, unicode):
-            host = host.encode('utf-8')
-        uri = 'http://%s%s' % (ipautil.format_netloc(host, port), url)
-        post = urlencode(kw)
-        root_logger.info('request %r', uri)
-        root_logger.debug('request post %r', post)
-        conn = httplib.HTTPConnection(host, port)
-        try:
-            conn.request('POST', url,
-                body=post,
-                headers={'Content-type': 'application/x-www-form-urlencoded'},
-            )
-            res = conn.getresponse()
-
-            http_status = res.status
-            http_reason_phrase = unicode(res.reason, 'utf-8')
-            http_headers = res.msg.dict
-            http_body = res.read()
-            conn.close()
-        except NSPRError, e:
-            raise NetworkError(uri=uri, error=str(e))
-
-        root_logger.debug('request status %d',        http_status)
-        root_logger.debug('request reason_phrase %r', http_reason_phrase)
-        root_logger.debug('request headers %s',       http_headers)
-        root_logger.debug('request body %r',          http_body)
-
-        return http_status, http_reason_phrase, http_headers, http_body
diff --git a/ipapython/platform/fedora16.py b/ipapython/platform/fedora16.py
index 794c39e2091f9402282e18fbe162d40892cb1e0d..43bfec5db1dc7065f2d904b8e74e6538a2640c9b 100644
--- a/ipapython/platform/fedora16.py
+++ b/ipapython/platform/fedora16.py
@@ -17,9 +17,13 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
-from ipapython import ipautil
-from ipapython.platform import base, redhat, systemd
 import os
+import time
+
+from ipapython import ipautil, dogtag
+from ipapython.platform import base, redhat, systemd
+from ipapython.ipa_log_manager import root_logger
+from ipalib import api
 
 # All what we allow exporting directly from this module
 # Everything else is made available through these symbols when they are
@@ -128,15 +132,58 @@ class Fedora16SSHService(Fedora16Service):
     def get_config_dir(self, instance_name=""):
         return '/etc/ssh'
 
+
+class Fedora16CAService(Fedora16Service):
+    def __wait_until_running(self):
+        # We must not wait for the httpd proxy if httpd is not set up yet.
+        # Unfortunately, knownservices.httpd.is_installed() can return
+        # false positives, so check for existence of our configuration file.
+        # TODO: Use a cleaner solution
+        if not os.path.exists('/etc/httpd/conf.d/ipa.conf'):
+            root_logger.debug(
+                'The httpd proxy is not installed, skipping wait for CA')
+            return
+        if dogtag.install_constants.DOGTAG_VERSION < 10:
+            # The server status information isn't available on DT 9
+            root_logger.debug('Using Dogtag 9, skipping wait for CA')
+            return
+        root_logger.debug('Waiting until the CA is running')
+        timeout = api.env.startup_timeout
+        op_timeout = time.time() + timeout
+        while time.time() < op_timeout:
+            status = dogtag.ca_status()
+            root_logger.debug('The CA status is: %s' % status)
+            if status == 'running':
+                break
+            root_logger.debug('Waiting for CA to start...')
+            time.sleep(1)
+        else:
+            raise RuntimeError('CA did not start in %ss' % timeout)
+
+    def start(self, instance_name="", capture_output=True, wait=True):
+        super(Fedora16CAService, self).start(
+            instance_name, capture_output=capture_output, wait=wait)
+        if wait:
+            self.__wait_until_running()
+
+    def restart(self, instance_name="", capture_output=True, wait=True):
+        super(Fedora16CAService, self).restart(
+            instance_name, capture_output=capture_output, wait=wait)
+        if wait:
+            self.__wait_until_running()
+
+
 # Redirect directory server service through special sub-class due to its
 # special handling of instances
 def f16_service(name):
     if name == 'dirsrv':
         return Fedora16DirectoryService(name)
     if name == 'ipa':
         return Fedora16IPAService(name)
     if name == 'sshd':
         return Fedora16SSHService(name)
+    if name in ('pki-cad', 'pki_cad', 'pki-tomcatd', 'pki_tomcatd'):
+        return Fedora16CAService(name)
     return Fedora16Service(name)
 
 class Fedora16Services(base.KnownServices):
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
index f2ac840aef1a52ec78b6232c94029cf6a009c501..89fc240e11971772283bde40e9f759a91fcf6cda 100644
--- a/ipaserver/install/cainstance.py
+++ b/ipaserver/install/cainstance.py
@@ -41,6 +41,7 @@
 from ipalib import pkcs10, x509
 from ipapython.dn import DN
 import subprocess
+import traceback
 
 from nss.error import NSPRError
 import nss.nss as nss
@@ -390,6 +391,7 @@ def restart_instance(self):
                 sys.exit(1)
         except Exception:
             # TODO: roll back here?
+            root_logger.debug(traceback.format_exc())
             root_logger.critical("Failed to restart the directory server. See the installation log for details.")
 
     def uninstall(self):
@@ -857,6 +859,7 @@ def __restart_instance(self):
             self.restart(self.dogtag_constants.PKI_INSTANCE_NAME)
         except Exception:
             # TODO: roll back here?
+            root_logger.debug(traceback.format_exc())
             root_logger.critical("Failed to restart the certificate server. See the installation log for details.")
 
     def __disable_nonce(self):
@@ -1541,6 +1544,11 @@ def install_replica_ca(config, postinstall=False):
                           master_host=config.master_host_name,
                           subject_base=config.subject_base)
 
+    if postinstall:
+        # Restart httpd since we changed its config
+        ipaservices.knownservices.httpd.restart()
+
+
     # The dogtag DS instance needs to be restarted after installation.
     # The procedure for this is: stop dogtag, stop DS, start DS, start
     # dogtag
-- 
1.7.11.7

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

Reply via email to