[node-patches] Change in ovirt-node[master]: [DRAFT] First pass at rhn_model

rbarry at redhat.com rbarry at redhat.com
Tue Apr 28 18:29:54 UTC 2015


Ryan Barry has uploaded a new change for review.

Change subject: [DRAFT] First pass at rhn_model
......................................................................

[DRAFT] First pass at rhn_model

Reworking the RHN classic/satellite stuff. Onto SAM next, which
will be faster, then reworking the transaction flow

Change-Id: I52cc84a07ff0c45f3345fd2ec09fc97aa5b75a3a
Signed-off-by: Ryan Barry <rbarry at redhat.com>
---
M src/ovirt/node/setup/rhn/rhn_model.py
1 file changed, 238 insertions(+), 141 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/ovirt-node refs/changes/76/40376/1

diff --git a/src/ovirt/node/setup/rhn/rhn_model.py b/src/ovirt/node/setup/rhn/rhn_model.py
old mode 100644
new mode 100755
index a808200..6823ce2
--- a/src/ovirt/node/setup/rhn/rhn_model.py
+++ b/src/ovirt/node/setup/rhn/rhn_model.py
@@ -18,41 +18,16 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 # MA  02110-1301, USA.  A copy of the GNU General Public License is
 # also available at http://www.gnu.org/copyleft/gpl.html.
-from ovirt.node import utils
+from ovirt.node import base, utils
 from ovirt.node.config.defaults import NodeConfigFileSection
 from ovirt.node.utils import process, system
 from ovirt.node.utils.fs import Config
-from urlparse import urlparse
 import sys
+import re
 import os.path
 import glob
-import subprocess
-import urllib
-
-
-RHN_XMLRPC_ADDR = "https://xmlrpc.rhn.redhat.com/XMLRPC"
-RHN_SSL_CERT = "/usr/share/rhn/RHNS-CA-CERT"
-
-
-def parse_host_port(url):
-    if url.count('://') == 1:
-        (proto, url) = url.split('://')
-    else:
-        proto = ''
-    if url.count(':') == 1:
-        (url, port) = url.split(':')
-        try:
-            port = int(port)
-        except:
-            port = 0
-    elif proto == 'http':
-        port = 80
-    elif proto == 'https':
-        port = 443
-    else:
-        port = 0
-    host = url.split('/')[0]
-    return (host, port)
+import requests
+import urlparse
 
 
 class RHN(NodeConfigFileSection):
@@ -80,135 +55,231 @@
         cfg = dict(NodeConfigFileSection.retrieve(self))
         return cfg
 
-    def retrieveCert(self, url, dest):
-        for x in range(0, 3):
-            try:
-                # urllib doesn't check ssl certs, so we're ok here
-                urllib.urlretrieve(url, dest)
-                return
-            except IOError:
-                self.logger.debug(
-                    "Failed to download {url} on try {x}".format(
-                        url=url, x=x))
+    def retrieve_cert(self, url, dest):
+        """
+        Grab the SSL certificate by trying 3 times (use another method to
+        actually do the retrieval). If it doesn't get pulled, raise an
+        exception
+        """
+        success = False
 
-        # If we're here, we failed to get it
-        raise RuntimeError("Error downloading SSL certificate!")
+        for x in range(0, 3):
+            msg = self._retrieve_url(url, dest)
+            if msg:
+                self.logger.info("Failed to retrieve download {url} on "
+                                 "try {x}".format(url=url, x=x))
+                self.logger.info(msg)
+            else:
+                # We could return and rely on the falling through to
+                # raise the exception, but it's clearer to be explicit
+                success = True
+                break
+        if not success:
+            raise RuntimeError("Error downloading SSL certificate")
+
+    def _retrieve_url(self, url, dest):
+        """
+        Retrieve a file from a URL. Use python-requests so we can be as
+        explicit as possible about problems, and it's the most pythonic
+        library in stdlib (don't use urllib or urllib2 for this)
+        """
+        msg = None
+
+        with open(dest, 'w') as f:
+            try:
+                s = requests.Session()
+                r = s.get(url, stream=True)
+                if r.status_code != 200:
+                    msg = "Cannot download the file: HTTP error code %s" \
+                          % str(r.status_code)
+                    os.unlink(dest)
+                f.write(r.raw.read())
+            except requests.exceptions.ConnectionError as e:
+                msg = "Connection Error: %s" % str(e[0])
+                os.unlink(dest)
+            except:
+                import traceback
+                msg = "Unexpected error: %s" % str(traceback.format_exc())
+            finally:
+                return msg
+
+    def parse_host_uri_re(self, uri):
+        # Regexes (and complex regexes) aren't always the clearest solution,
+        # but they're a lot clearer than trying to parse out a URI without them
+        # through weird substring matching and partial matches which don't
+        # paint a complete picture of what's happening in one shot. Do the
+        # legwork just in case, but urlparse (below) is better
+
+        matcher = re.compile(r"""
+        ^
+        (?:                                 #Start a nonmatching capture group
+        (?P<proto>\w+)                      # But capture the protocol
+        (?:://))?                           # Not the URI separator
+        (?P<host>.*?)                       # Get the host, not optional
+        (?:(?::)(?P<port>\d+))?             # Maybe a port, maybe not
+        (?:/.*?)?                           # Path may continue, but scrap it
+        $""", re.VERBOSE)
+
+        matches = matcher.match(uri).groupdict()
+
+        ports = {"http": 80,
+                 "https": 443}
+
+        matches["port"] = matches["port"] or ports[matches["proto"]] if \
+            matches["proto"] in ports else 0
+
+        return matches["host"], matches["port"]
+
+    def parse_host_uri(self, uri):
+        ports = {"http": 80,
+                 "https": 443}
+
+        # urlparse expects the URI to start with proto://, or else it's
+        # a relative path. But only the // separator is required to parse
+        # it out correctly. If it's not there, add it. Still safer than
+        # re-implementing uri parsing ourselves
+        urischeme = re.compile(r'^\w+://')
+        uri = uri if urischeme.match(uri) else "//" + uri
+
+        parsed = urlparse.urlparse(uri)
+        port = parsed.port or ports[parsed.scheme] if parsed.scheme in ports \
+            else 0
+        return parsed.hostname, port
 
     def transaction(self, password, proxypass=None):
 
+        cfg = dict(self.retrieve())
+
+        class Vars:
+            argbuilder = None
+
         class ConfigureRHNClassic(utils.Transaction.Element):
+            title = "Setting up rhnreg config"
+
+            def commit(self):
+                # Append the path so we can import rhnreg. Why isn't it
+                # also packaged in site-packages? Should ask the maintainer
+                sys.path.append("/usr/share/rhn/up2date_client")
+                import rhnreg
+
+                rhnreg.cfg.set("serverURL",
+                               "https://xmlrpc.rhn.redhat.com/XMLRPC")
+                rhnreg.cfg.set("sslCACert",
+                               "/usr/share/rhn/RHNS-CA-CERT")
+                rhnreg.cfg.save()
+                self.logger.debug("Updated rhnreg config using their API")
+
+        class DownloadCertificate(utils.Transaction.Element):
+            title = "Checking SSL Certificate"
+
+            def commit(self):
+                # If there's no CA cert path specified, assume the defaut
+                cfg["ca_cert"] = cfg["ca_cert"] if cfg["ca_cert"] else \
+                    cfg["url"] + "/pub/RHN-ORG-TRUSTED-SSL-CERT"
+
+                location = "/etc/sysconfig/rhn/%s" % os.path.basename(
+                    cfg["ca_cert"])
+
+                if not os.path.exists(location):
+                    self.logger.info("Downloading CA cert from: %s as %s",
+                                     (cfg["ca_cert"], location))
+                    RHN().retrieve_cert(cfg["ca_cert"], location)
+
+                # Why would this ever happen without an exception from the
+                # method retrieving the cert?
+                if os.stat(location).st_size == 0:
+                    raise RuntimeError("SSL certificate %s has has zero size, "
+                                       "can't use it. Please check.")
+                else:
+                    Config().persist(location)
+
+        class PrepareClassicRegistration(utils.Transaction.Element):
+            title = "Preparing for registration"
+
+            def commit(self):
+                # FIXME: we should do this validation in the page in
+                # on_merge instead of trying to catch in the model.
+                # In theory, we could return this if only some values for
+                # autoinstaller registering were passed, but not registering
+                # then is expected behavior
+                if not cfg["activationkey"] and not cfg["username"]:
+                    self.logger.debug("No activationkey or username+password "
+                                      "given. Exiting")
+                    raise RuntimeError("No activation key or username+"
+                                       "password was given. Can't register!")
+
+                # Why these flags?
+                # --novirtinfo: rhn-virtualization daemon refreshes virtinfo
+                # --nopackages because it's a ro image and an appliance
+                #    can't update them anyway
+                # --norhnsd because we don't want to try (and fail) to run
+                #    actions on groups from Spacewalk/Satellite
+                initial_args = ["/usr/sbin/rhnreg_ks"]
+                initial_args.extend(["--novirtinfo", "--norhnsd",
+                                     "--nopackages", "--force"])
+
+                # If the URL doesn't end with the default path, add it
+                cfg["url"] = cfg["url"] if cfg["url"].endswith("/XMLRPC") \
+                    else cfg["url"] + "/XMLRPC"
+
+                mapping = {"--serverUrl":     cfg["url"],
+                           "--sslCACert":     cfg["ca_cert"],
+                           "--activationkey": cfg["activationkey"],
+                           "--username":      cfg["username"],
+                           "--password":      password,
+                           "--profilename":   cfg["profile"],
+                           "--proxy":         cfg["proxy"],
+                           "--proxyUser":     cfg["proxyuser"],
+                           "--proxyPassword": proxypass
+                           }
+
+                Vars.argbuilder = ArgBuilder(initial_args, mapping)
+
+        class RegisterRHNClassic(utils.Transaction.Element):
             state = ("RHN" if RHN().retrieve()["rhntype"] == "rhn"
                      else "Satellite")
             title = "Configuring %s" % state
 
             def commit(self):
-                cfg = RHN().retrieve()
-                self.logger.debug(cfg)
-                rhntype = cfg["rhntype"]
-                serverurl = cfg["url"]
-                cacert = cfg["ca_cert"]
-                activationkey = cfg["activationkey"]
-                username = cfg["username"]
-                profilename = cfg["profile"]
-                proxy = cfg["proxy"]
-                proxyuser = cfg["proxyuser"]
-
-                # novirtinfo: rhn-virtualization daemon refreshes virtinfo
-                extra_args = ['--novirtinfo', '--norhnsd', '--nopackages',
-                              '--force']
-                args = ['/usr/sbin/rhnreg_ks']
-                if rhntype == "rhn":
-                    sys.path.append("/usr/share/rhn/up2date_client")
-                    import rhnreg
-                    rhnreg.cfg.set("serverURL", RHN_XMLRPC_ADDR)
-                    rhnreg.cfg.set("sslCACert", RHN_SSL_CERT)
-                    rhnreg.cfg.save()
-                    self.logger.info("ran update")
-                if serverurl:
-                    cacert = cacert if cacert is not None else serverurl + \
-                        "/pub/RHN-ORG-TRUSTED-SSL-CERT"
-                    if not serverurl.endswith("/XMLRPC"):
-                        serverurl = serverurl + "/XMLRPC"
-                    args.append('--serverUrl')
-                    args.append(serverurl)
-                    location = "/etc/sysconfig/rhn/%s" % \
-                               os.path.basename(cacert)
-                    if cacert:
-                        if not os.path.exists(cacert):
-                            self.logger.info("Downloading CA cert.....")
-                            self.logger.debug("From: %s To: %s" %
-                                              (cacert, location))
-                            RHN().retrieveCert(cacert, location)
-                        if os.path.isfile(location):
-                            if os.stat(location).st_size > 0:
-                                args.append('--sslCACert')
-                                args.append(location)
-                                Config().persist(location)
-                            else:
-                                raise RuntimeError("Error Downloading \
-                                                   CA cert!")
-                if activationkey:
-                    args.append('--activationkey')
-                    args.append(activationkey)
-                elif username:
-                    args.append('--username')
-                    args.append(username)
-                    if password:
-                        args.append('--password')
-                        args.append(password)
-                else:
-                    # skip RHN registration when neither activationkey
-                    # nor username/password is supplied
-                    self.logger.debug("No activationkey or "
-                                      "username+password given")
-                    return
-
-                if profilename:
-                    args.append('--profilename')
-                    args.append(profilename)
-
-                if proxy:
-                    args.append('--proxy')
-                    args.append(proxy)
-                    if proxyuser:
-                        args.append('--proxyUser')
-                        args.append(proxyuser)
-                        if proxypass:
-                            args.append('--proxyPassword')
-                            args.append(proxypass)
-                args.extend(extra_args)
-
                 self.logger.info("Registering to RHN account.....")
-                conf = Config()
-                conf.unpersist("/etc/sysconfig/rhn/systemid")
-                conf.unpersist("/etc/sysconfig/rhn/up2date")
-                logged_args = list(args)
-                remove_values_from_args = ["--password", "--proxyPassword"]
-                for idx, arg in enumerate(logged_args):
-                    if arg in remove_values_from_args:
-                        logged_args[idx+1] = "XXXXXXX"
-                logged_args = str(logged_args)
+                Config().unpersist("/etc/sysconfig/rhn/systemid")
+                Config().unpersist("/etc/sysconfig/rhn/up2date")
+
+                # Filter out passwords from the log
+                logged_args = Vars.argbuilder.get_commandlist(filtered=True)
                 self.logger.debug(logged_args)
                 try:
-                    subprocess.check_call(args)
-                    conf.persist("/etc/sysconfig/rhn/up2date")
-                    conf.persist("/etc/sysconfig/rhn/systemid")
+                    process.check_call(Vars.argbuilder.get_commandlist())
+                    Config().persist("/etc/sysconfig/rhn/up2date")
+                    Config().persist("/etc/sysconfig/rhn/systemid")
                     self.logger.info("System %s sucessfully registered to %s" %
-                                     (profilename, serverurl))
-                    # sync profile if reregistering, fixes problem with
-                    # virt guests not showing
-                    sys.path.append("/usr/share/rhn")
-                    from virtualization import support
-                    support.refresh(True)
-                    # find old SAM/Sat 6 registrations
-                    if Config().exists("/etc/rhsm/rhsm.conf"):
+                                     (cfg["profile"], cfg["url"]))
+
+                except process.CalledProcessError:
+                    self.logger.exception("Failed to call: %s" % logged_args)
+                    raise RuntimeError("Error registering to RHN account")
+
+                # Syncing the profile resolves a problem with guests not
+                # showing
+                sys.path.append("/usr/share/rhn")
+                from virtualization import support
+                support.refresh(True)
+
+        class RemoveSAMConfigs(utils.Transaction.Element):
+            title = "Removing old configuration..."
+
+            def commit(self):
+                # find old SAM/Sat 6 registrations
+                if Config().exists("/etc/rhsm/rhsm.conf"):
+                    try:
                         process.call(["subscription-manager",
                                       "remove", "--all"])
                         process.call(["subscription-manager", "clean"])
                         Config().unpersist("/etc/rhsm/rhsm.conf")
-                except:
-                    self.logger.exception("Failed to call: %s" % logged_args)
-                    raise RuntimeError("Error registering to RHN account")
+                    except process.CalledProcessError:
+                        raise RuntimeError("Couldn't remove old configuration!"
+                                           " Check the output of "
+                                           "subscription-manager remove --all")
 
         class ConfigureSAM(utils.Transaction.Element):
             # sam path is used for sat6 as well, making generic
@@ -263,7 +334,7 @@
 
                 if serverurl:
                     (host, port) = parse_host_port(serverurl)
-                    parsed_url = urlparse(serverurl)
+                    parsed_url = urlparse.urlparse(serverurl)
                     prefix = parsed_url.path
                     if cacert.endswith(".pem") and rhntype == "satellite":
                         prefix = "/rhsm"
@@ -409,3 +480,29 @@
         else:
             tx.append(ConfigureRHNClassic())
         return tx
+
+
+class ArgBuilder(base.Base):
+    args = None
+    filtered_args = ["--password", "--proxyPassword"]
+
+    def __init__(self, mapping, initial_args):
+        self.args = initial_args
+        self._build_map(mapping)
+
+    def _build_map(self, mapping):
+        # Can't filter() on a dict, so use a dict comprehension to do it
+        # and append in one line
+        _ = map(lambda (x,y): self.args.extend([x,y]),
+                dict((k,v) for k,v in mapping.items() if v).iteritems())
+
+    def get_commandlist(self, filtered=False):
+        command = " ".join(self.args)
+
+        if not filtered:
+            return command
+        else:
+            for arg in self.filtered_args:
+                r = re.compile(r'(%s) \w+' % arg)
+                command = re.sub(r, r'\1 XXXXXXXX', command)
+            return command


-- 
To view, visit https://gerrit.ovirt.org/40376
To unsubscribe, visit https://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I52cc84a07ff0c45f3345fd2ec09fc97aa5b75a3a
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-node
Gerrit-Branch: master
Gerrit-Owner: Ryan Barry <rbarry at redhat.com>



More information about the node-patches mailing list