[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